upd
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
POSTGRES_URI=postgres://postgres:admin@192.168.1.250:5432/postgres
|
||||||
|
DB_HOST=192.168.1.250:5432
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=admin
|
||||||
|
DB_DATABASE=postgres
|
||||||
|
PORT=3001
|
||||||
|
JWT_ACCESS_SECRET=aboba
|
||||||
|
JWT_REFRESH_SECRET=aboba
|
||||||
|
JWT_ACCESS_EXP_TIME=30d
|
||||||
|
JWT_REFRESH_EXP_TIME=30d
|
||||||
|
NODE_ENV=development
|
||||||
|
S3_REGION=ru-central1
|
||||||
|
S3_ENDPOINT=https://storage.yandexcloud.net
|
||||||
|
S3_ACCESS_KEY_ID=YCAJE7XefUV51hyi9GEdld8S3
|
||||||
|
S3_ACCESS_KEY=YCPY__ni1vs95aDjhutAlF8xX0kg3XP6Lbj9PifZ
|
||||||
|
S3_BUCKET=dult-faib-knac-fint
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"postman.settings.dotenv-detection-notification-visibility": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Glob } from 'bun';
|
||||||
|
|
||||||
|
for (const entrypoint of new Glob('src/**/*.ts').scanSync())
|
||||||
|
await Bun.build({
|
||||||
|
entrypoints: [entrypoint],
|
||||||
|
target: 'bun',
|
||||||
|
outdir: './dist/' + entrypoint.split('\\').slice(1, -1).join('\\'),
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
dialect: 'postgresql',
|
||||||
|
schema: './src/db/schema/index.ts',
|
||||||
|
out: './drizzle',
|
||||||
|
dbCredentials: {
|
||||||
|
url: process.env.POSTGRES_URI!,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
declare namespace NodeJS {
|
||||||
|
interface ProcessEnv {
|
||||||
|
readonly POSTGRES_URI: string;
|
||||||
|
readonly DB_HOST: string;
|
||||||
|
readonly DB_USER: string;
|
||||||
|
readonly DB_PASSWORD: string;
|
||||||
|
readonly DB_DATABASE: string;
|
||||||
|
readonly PORT: string;
|
||||||
|
readonly JWT_ACCESS_EXP_TIME: string;
|
||||||
|
readonly JWT_REFRESH_EXP_TIME: string;
|
||||||
|
readonly JWT_ACCESS_SECRET: string;
|
||||||
|
readonly JWT_REFRESH_SECRET: string;
|
||||||
|
readonly NODE_ENV: string;
|
||||||
|
readonly S3_REGION: string;
|
||||||
|
readonly S3_ENDPOINT: string;
|
||||||
|
readonly S3_ACCESS_KEY_ID: string;
|
||||||
|
readonly S3_ACCESS_KEY: string;
|
||||||
|
readonly S3_BUCKET: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-4
@@ -3,13 +3,28 @@
|
|||||||
"version": "1.0.50",
|
"version": "1.0.50",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "bun run --watch src/index.ts"
|
"dev": "bun --hot ./src",
|
||||||
|
"push": "drizzle-kit push"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"elysia": "latest"
|
"@aws-sdk/client-s3": "^3.709.0",
|
||||||
|
"@elysiajs/cors": "1.1.1",
|
||||||
|
"cron": "^3.3.1",
|
||||||
|
"drizzle-orm": "^0.38.4",
|
||||||
|
"drizzle-typebox": "^0.2.0",
|
||||||
|
"elysia": "latest",
|
||||||
|
"jose": "^5.9.6",
|
||||||
|
"nodemailer": "^6.10.0",
|
||||||
|
"postgres": "^3.4.5",
|
||||||
|
"sharp": "^0.33.5",
|
||||||
|
"transliteration": "^2.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun-types": "latest"
|
"@types/bun": "^1.2.2",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
|
"bun-types": "latest",
|
||||||
|
"drizzle-kit": "^0.30.0",
|
||||||
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
"module": "src/index.js"
|
"module": "src/index.js"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { S3Client } from '@aws-sdk/client-s3';
|
||||||
|
|
||||||
|
export const s3client = new S3Client({
|
||||||
|
region: process.env.S3_REGION,
|
||||||
|
endpoint: process.env.S3_ENDPOINT,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||||
|
secretAccessKey: process.env.S3_ACCESS_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import Elysia, { t } from 'elysia';
|
||||||
|
import { authMiddleware } from '../middlewares/auth';
|
||||||
|
import { getAll, getOne, create, remove, update } from '../services/articles';
|
||||||
|
import { Block } from '../types/article';
|
||||||
|
import { getCount } from '../services/articles/getCount';
|
||||||
|
import { createInsertSchema, createSelectSchema } from 'drizzle-typebox';
|
||||||
|
import { articlesTable } from '../db/schema';
|
||||||
|
import { getDrafted } from '../services/articles/getDrafted';
|
||||||
|
|
||||||
|
const getArticle = createSelectSchema(articlesTable);
|
||||||
|
|
||||||
|
const insertArticle = createInsertSchema(articlesTable, {
|
||||||
|
createdAt: (_) => t.Date(),
|
||||||
|
blocks: t.String(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const articlesController = new Elysia({ prefix: '/articles' })
|
||||||
|
.get(
|
||||||
|
'/',
|
||||||
|
async ({ query: { tags = [], offset = 0, limit = 10 } }) =>
|
||||||
|
await getAll(tags, offset, limit),
|
||||||
|
{
|
||||||
|
query: t.Partial(
|
||||||
|
t.Object({
|
||||||
|
tags: t.Array(t.String()),
|
||||||
|
offset: t.Number({ default: 0 }),
|
||||||
|
limit: t.Number({ default: 10 }),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: t.Array(getArticle),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.get('/:slug', async ({ params: { slug } }) => await getOne(slug), {
|
||||||
|
params: t.Object({ slug: t.String() }),
|
||||||
|
response: {
|
||||||
|
200: getArticle,
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.get('/count', async ({ query: { tags = [] } }) => await getCount(tags), {
|
||||||
|
query: t.Partial(
|
||||||
|
t.Object({
|
||||||
|
tags: t.Array(t.String()),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: t.Number(),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.use(authMiddleware)
|
||||||
|
.get(
|
||||||
|
'/drafted',
|
||||||
|
async ({ query: { limit = 100, offset = 0, tags = [] } }) =>
|
||||||
|
await getDrafted(tags, offset, limit),
|
||||||
|
{
|
||||||
|
query: t.Partial(
|
||||||
|
t.Object({
|
||||||
|
tags: t.Array(t.String()),
|
||||||
|
offset: t.Number({ default: 0 }),
|
||||||
|
limit: t.Number({ default: 100 }),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: t.Array(getArticle),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.post(
|
||||||
|
'/',
|
||||||
|
async ({ body }) =>
|
||||||
|
await create({
|
||||||
|
...body,
|
||||||
|
blocks: JSON.parse(body.blocks),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
body: insertArticle,
|
||||||
|
response: {
|
||||||
|
200: getArticle,
|
||||||
|
400: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.delete('/:id', async ({ params: { id } }) => await remove(id), {
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
response: {
|
||||||
|
200: getArticle,
|
||||||
|
400: t.ObjectString({}),
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.put(
|
||||||
|
'/:id',
|
||||||
|
async ({ params: { id }, body }) =>
|
||||||
|
await update(id, {
|
||||||
|
...body,
|
||||||
|
blocks: JSON.parse(body.blocks),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
body: insertArticle,
|
||||||
|
response: {
|
||||||
|
200: getArticle,
|
||||||
|
400: t.ObjectString({}),
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import Elysia, { t } from 'elysia';
|
||||||
|
import { authMiddleware } from '../middlewares/auth';
|
||||||
|
import { login, logout, refresh } from '../services/auth';
|
||||||
|
|
||||||
|
export const authController = new Elysia({ prefix: '/auth' })
|
||||||
|
.post('/login', async ({ body, cookie }) => await login(body, cookie), {
|
||||||
|
body: t.Object({
|
||||||
|
username: t.String(),
|
||||||
|
password: t.String({ minLength: 6 }),
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: t.Object({ accessToken: t.String(), refreshToken: t.String() }),
|
||||||
|
401: t.ObjectString({}),
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.use(authMiddleware)
|
||||||
|
.get('/check', async (context) => {
|
||||||
|
return {
|
||||||
|
auth: 'adminId' in context && context.adminId,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.get(
|
||||||
|
'/logout',
|
||||||
|
async ({ cookie, adminId }) => await logout(cookie, adminId),
|
||||||
|
{
|
||||||
|
cookie: t.Cookie({ accessToken: t.String(), refreshToken: t.String() }),
|
||||||
|
adminId: t.String(),
|
||||||
|
response: {
|
||||||
|
200: t.Object({ success: t.Boolean() }),
|
||||||
|
400: t.ObjectString({}),
|
||||||
|
401: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
'/refresh',
|
||||||
|
async ({ cookie, adminId }) => await refresh(cookie, adminId)
|
||||||
|
);
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import Elysia, { t } from 'elysia';
|
||||||
|
import {
|
||||||
|
getMany,
|
||||||
|
getCount,
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
remove,
|
||||||
|
} from '../services/companies';
|
||||||
|
import { authMiddleware } from '../middlewares/auth';
|
||||||
|
import { createInsertSchema, createSelectSchema } from 'drizzle-typebox';
|
||||||
|
import { companiesTable } from '../db/schema';
|
||||||
|
import { getByCity } from '../services/companies/getByCity';
|
||||||
|
|
||||||
|
const createCompany = createInsertSchema(companiesTable);
|
||||||
|
|
||||||
|
const getCompany = createSelectSchema(companiesTable, {
|
||||||
|
id: t.String({
|
||||||
|
format: 'uuid',
|
||||||
|
default: '00000000-0000-0000-0000-000000000000',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const companiesController = new Elysia({ prefix: '/companies' })
|
||||||
|
.get(
|
||||||
|
'/',
|
||||||
|
async ({ query: { city } }) =>
|
||||||
|
city ? await getByCity(city) : await getMany(),
|
||||||
|
{
|
||||||
|
query: t.Partial(t.Object({ city: t.String() })),
|
||||||
|
response: {
|
||||||
|
200: t.Array(getCompany),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.get('/count', async () => await getCount(), {
|
||||||
|
response: { 200: t.Number(), 500: t.ObjectString({}) },
|
||||||
|
})
|
||||||
|
.use(authMiddleware)
|
||||||
|
.post('/', async ({ body }) => await create(body), {
|
||||||
|
body: createCompany,
|
||||||
|
response: {
|
||||||
|
200: getCompany,
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.put('/:id', async ({ params: { id }, body }) => await update(id, body), {
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
body: createCompany,
|
||||||
|
response: {
|
||||||
|
200: getCompany,
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.delete('/:id', async ({ params: { id } }) => await remove(id), {
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
response: {
|
||||||
|
200: getCompany,
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import Elysia, { t } from 'elysia';
|
||||||
|
|
||||||
|
export const getReionNameController = new Elysia({
|
||||||
|
prefix: '/getRegionName',
|
||||||
|
}).get(
|
||||||
|
'/',
|
||||||
|
async ({ headers }) => {
|
||||||
|
const ip = headers['X-Forwarded-For'];
|
||||||
|
try {
|
||||||
|
const res = (await fetch(`http://ip-api.com/json/${ip}?lang=ru`)).json();
|
||||||
|
console.log(res);
|
||||||
|
} catch (error) {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: t.Object({ 'X-Forwarded-For': t.String({ format: 'ipv4' }) }),
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './articles';
|
||||||
|
export * from './auth';
|
||||||
|
export * from './projects';
|
||||||
|
export * from './upload';
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import Elysia, { t } from 'elysia';
|
||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
|
||||||
|
export const mailController = new Elysia({ prefix: '/mail' }).post(
|
||||||
|
'/',
|
||||||
|
async ({
|
||||||
|
headers: { referer },
|
||||||
|
body: { email, fullname, phone, products },
|
||||||
|
}) => {
|
||||||
|
const url = new URL(referer);
|
||||||
|
|
||||||
|
let transporter = nodemailer.createTransport({
|
||||||
|
host: 'mail.netangels.ru',
|
||||||
|
port: 587,
|
||||||
|
secure: false, // true for 465, false for other ports
|
||||||
|
auth: {
|
||||||
|
user: 'test@graff.tech', // generated ethereal user
|
||||||
|
pass: 'ZmL0pKiDFWUyCDMq', // generated ethereal password
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let info = await transporter.sendMail({
|
||||||
|
from: email, // sender address
|
||||||
|
to: 'info@graff.tech', // list of receivers
|
||||||
|
subject: `Заявка с сайта ${url.host}`, // Subject line
|
||||||
|
text: `
|
||||||
|
Имя Фамилия: ${fullname}
|
||||||
|
Email: ${email}
|
||||||
|
Телефон: ${phone}
|
||||||
|
Продукты: ${products.join(', ')}
|
||||||
|
`, // plain text body
|
||||||
|
html: `<div>
|
||||||
|
<p>Имя: ${fullname}</p>
|
||||||
|
<p>Email: ${email}</p>
|
||||||
|
<p>Телефон: ${phone}</p>
|
||||||
|
<p>Продукты: ${products.join(', ')}</p>
|
||||||
|
</div>`, // html body
|
||||||
|
});
|
||||||
|
|
||||||
|
return info;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: t.Object({ referer: t.String() }),
|
||||||
|
body: t.Object({
|
||||||
|
fullname: t.String(),
|
||||||
|
email: t.String({ format: 'email' }),
|
||||||
|
phone: t.String(),
|
||||||
|
products: t.Array(t.String()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { getAll } from '../services/mapVideos/getAll';
|
||||||
|
import { createInsertSchema, createSelectSchema } from 'drizzle-typebox';
|
||||||
|
import Elysia, { t } from 'elysia';
|
||||||
|
import { mapVideosTable } from '../db/schema';
|
||||||
|
import { authMiddleware } from '../middlewares/auth';
|
||||||
|
import { createMapVideo } from '../services/mapVideos/create';
|
||||||
|
import { updateMapVideo } from '../services/mapVideos/update';
|
||||||
|
import { deleteMapVideo } from '../services/mapVideos/delete';
|
||||||
|
|
||||||
|
const getMapVideosSchema = createSelectSchema(mapVideosTable);
|
||||||
|
|
||||||
|
const createMapVideoSchema = createInsertSchema(mapVideosTable);
|
||||||
|
|
||||||
|
export const mapVideosController = new Elysia({ prefix: '/mapVideos' })
|
||||||
|
.get('/', async () => await getAll(), {
|
||||||
|
response: {
|
||||||
|
200: t.Array(getMapVideosSchema),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.use(authMiddleware)
|
||||||
|
.post('/', async ({ body }) => await createMapVideo(body), {
|
||||||
|
body: createMapVideoSchema,
|
||||||
|
response: {
|
||||||
|
200: getMapVideosSchema,
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.put(
|
||||||
|
'/:id',
|
||||||
|
async ({ body, params: { id } }) => await updateMapVideo(id, body),
|
||||||
|
{
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
body: createMapVideoSchema,
|
||||||
|
response: {
|
||||||
|
200: getMapVideosSchema,
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.delete('/:id', async ({ params: { id } }) => await deleteMapVideo(id), {
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
response: {
|
||||||
|
200: getMapVideosSchema,
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import Elysia, { t } from 'elysia';
|
||||||
|
import {
|
||||||
|
create,
|
||||||
|
getCount,
|
||||||
|
getMany,
|
||||||
|
getOne,
|
||||||
|
remove,
|
||||||
|
update,
|
||||||
|
} from '../services/projects';
|
||||||
|
import { authMiddleware } from '../middlewares/auth';
|
||||||
|
import { createInsertSchema, createSelectSchema } from 'drizzle-typebox';
|
||||||
|
import { projectsTable } from '../db/schema';
|
||||||
|
|
||||||
|
const createProject = createInsertSchema(projectsTable, {
|
||||||
|
releaseDate: (_) => t.Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const getProject = createSelectSchema(projectsTable, {
|
||||||
|
id: (_) =>
|
||||||
|
t.String({
|
||||||
|
format: 'uuid',
|
||||||
|
default: '00000000-0000-0000-0000-000000000000',
|
||||||
|
}),
|
||||||
|
companyId: (_) =>
|
||||||
|
t.Optional(
|
||||||
|
t.String({
|
||||||
|
format: 'uuid',
|
||||||
|
default: '00000000-0000-0000-0000-000000000000',
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const projectsController = new Elysia({ prefix: '/projects' })
|
||||||
|
.get(
|
||||||
|
'/',
|
||||||
|
async ({ query: { tags, city, limit, companyId } }) =>
|
||||||
|
await getMany(tags, city, limit, companyId),
|
||||||
|
{
|
||||||
|
query: t.Partial(
|
||||||
|
t.Object({
|
||||||
|
city: t.String(),
|
||||||
|
tags: t.Array(t.String()),
|
||||||
|
companyId: t.String({ format: 'uuid' }),
|
||||||
|
limit: t.Number(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
200: t.Array(getProject),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
'/count',
|
||||||
|
async ({ query: { city, tags } }) => await getCount(tags, city),
|
||||||
|
{
|
||||||
|
query: t.Object({
|
||||||
|
city: t.Optional(t.String()),
|
||||||
|
tags: t.Optional(t.Array(t.String())),
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: t.Integer(),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.get('/:id', async ({ params: { id } }) => await getOne(id), {
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
response: {
|
||||||
|
200: getProject,
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.use(authMiddleware)
|
||||||
|
.post('/', async ({ body }) => await create(body), {
|
||||||
|
body: createProject,
|
||||||
|
response: {
|
||||||
|
200: getProject,
|
||||||
|
422: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.delete('/:id', async ({ params: { id } }) => await remove(id), {
|
||||||
|
params: t.Object({
|
||||||
|
id: t.String({
|
||||||
|
format: 'uuid',
|
||||||
|
default: '00000000-0000-0000-0000-000000000000',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: getProject,
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.put('/:id', async ({ body, params: { id } }) => await update(id, body), {
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
body: createProject,
|
||||||
|
response: {
|
||||||
|
200: getProject,
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import Elysia, { error, t } from 'elysia';
|
||||||
|
import { db } from '../db';
|
||||||
|
import {
|
||||||
|
createInsertSchema,
|
||||||
|
createSelectSchema,
|
||||||
|
createUpdateSchema,
|
||||||
|
} from 'drizzle-typebox';
|
||||||
|
import { storiesTable } from '../db/schema';
|
||||||
|
import { asc, eq } from 'drizzle-orm';
|
||||||
|
import { authMiddleware } from '../middlewares/auth';
|
||||||
|
|
||||||
|
const getStory = createSelectSchema(storiesTable);
|
||||||
|
|
||||||
|
const createStory = createInsertSchema(storiesTable, {
|
||||||
|
createdAt: (_) => t.Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateStory = createUpdateSchema(storiesTable, {
|
||||||
|
createdAt: (_) => t.Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const storiesController = new Elysia({ prefix: '/stories' })
|
||||||
|
.get(
|
||||||
|
'/',
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
return await db.query.storiesTable.findMany({
|
||||||
|
orderBy: asc(storiesTable.createdAt),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
response: {
|
||||||
|
200: t.Array(getStory),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.use(authMiddleware)
|
||||||
|
.post(
|
||||||
|
'/',
|
||||||
|
async ({ body }) => {
|
||||||
|
try {
|
||||||
|
const res = await db.insert(storiesTable).values(body).returning();
|
||||||
|
if (!res) return error(400, { error: 'Story not created' });
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: createStory,
|
||||||
|
response: {
|
||||||
|
200: getStory,
|
||||||
|
400: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.delete(
|
||||||
|
'/:id',
|
||||||
|
async ({ params: { id } }) => {
|
||||||
|
try {
|
||||||
|
const candidate = await db.query.storiesTable.findFirst({
|
||||||
|
where: eq(storiesTable.id, id),
|
||||||
|
});
|
||||||
|
if (!candidate) return error(404, { error: 'Story not found' });
|
||||||
|
const res = await db
|
||||||
|
.delete(storiesTable)
|
||||||
|
.where(eq(storiesTable.id, id))
|
||||||
|
.returning();
|
||||||
|
if (!res) return error(400, { error: 'Story not deleted' });
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
response: {
|
||||||
|
200: getStory,
|
||||||
|
400: t.ObjectString({}),
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.put(
|
||||||
|
'/:id',
|
||||||
|
async ({ params: { id }, body }) => {
|
||||||
|
try {
|
||||||
|
const candidate = await db.query.storiesTable.findFirst({
|
||||||
|
where: eq(storiesTable.id, id),
|
||||||
|
});
|
||||||
|
if (!candidate) return error(404, { error: 'Story not found' });
|
||||||
|
const res = await db
|
||||||
|
.update(storiesTable)
|
||||||
|
.set(body)
|
||||||
|
.where(eq(storiesTable.id, id))
|
||||||
|
.returning();
|
||||||
|
if (!res) return error(400, { error: 'Story not updated' });
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: t.Object({ id: t.String({ format: 'uuid' }) }),
|
||||||
|
body: updateStory,
|
||||||
|
response: {
|
||||||
|
200: getStory,
|
||||||
|
400: t.ObjectString({}),
|
||||||
|
404: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import Elysia, { error, t } from 'elysia';
|
||||||
|
import { authMiddleware } from '../middlewares/auth';
|
||||||
|
import { s3client } from '../config/s3client';
|
||||||
|
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
||||||
|
import { randomUUIDv7 } from 'bun';
|
||||||
|
|
||||||
|
export const uploadController = new Elysia({ prefix: '/upload' })
|
||||||
|
.use(authMiddleware)
|
||||||
|
.post(
|
||||||
|
'/',
|
||||||
|
async ({ body: { dest, files } }) => {
|
||||||
|
if (!files.length) return error(422, { message: 'No files' });
|
||||||
|
try {
|
||||||
|
const filesPaths: string[] = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const title = `${randomUUIDv7()}.${file.name.split('.')[1]}`;
|
||||||
|
await s3client.send(
|
||||||
|
new PutObjectCommand({
|
||||||
|
Bucket: process.env.S3_BUCKET,
|
||||||
|
Key: `${dest}/${title}`,
|
||||||
|
Body: Buffer.from(await file.arrayBuffer()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
filesPaths.push(`${dest}/${title}`);
|
||||||
|
}
|
||||||
|
return filesPaths;
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (File uploading)' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
files: t.Files({ type: ['image', 'video'] }),
|
||||||
|
dest: t.String(),
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: t.Array(t.String()),
|
||||||
|
422: t.ObjectString({}),
|
||||||
|
500: t.ObjectString({}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||||
|
import * as schema from './schema';
|
||||||
|
import postgres from 'postgres';
|
||||||
|
|
||||||
|
const client = postgres(process.env.POSTGRES_URI);
|
||||||
|
|
||||||
|
export const db = drizzle(client, { schema });
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { relations } from 'drizzle-orm';
|
||||||
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
|
import { tokensTable } from './tokens.ts';
|
||||||
|
|
||||||
|
export const adminsTable = pgTable('admins', {
|
||||||
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
|
username: text('username').notNull().unique(),
|
||||||
|
hashedPassword: text('hashed_password').notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const adminsTokens = relations(adminsTable, ({ many }) => ({
|
||||||
|
tokens: many(tokensTable),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { pgTable, uuid, text, json, timestamp } from 'drizzle-orm/pg-core';
|
||||||
|
import { Block } from '../../types/article.ts';
|
||||||
|
import { boolean } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
|
export const articlesTable = pgTable('articles', {
|
||||||
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
|
title: text('title').notNull().unique(),
|
||||||
|
tags: text('tags').array().notNull(),
|
||||||
|
createdAt: timestamp('created_at', {
|
||||||
|
mode: 'date',
|
||||||
|
withTimezone: true,
|
||||||
|
})
|
||||||
|
.notNull()
|
||||||
|
.defaultNow(),
|
||||||
|
cardImage: text('card_image').notNull(),
|
||||||
|
posterImage: text('poster_image').notNull(),
|
||||||
|
blocks: json('blocks').$type<Block[]>().notNull(),
|
||||||
|
drafted: boolean('drafted').notNull().default(true),
|
||||||
|
slug: text('slug').unique(),
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { relations } from 'drizzle-orm';
|
||||||
|
import { text, uuid, varchar, pgTable } from 'drizzle-orm/pg-core';
|
||||||
|
import { projectsTable } from './projects';
|
||||||
|
|
||||||
|
export const companiesTable = pgTable('companies', {
|
||||||
|
id: uuid().defaultRandom().primaryKey(),
|
||||||
|
title: varchar('title', { length: 50 }).notNull(),
|
||||||
|
color: varchar('color', { length: 9 }).default('#ffffff'),
|
||||||
|
mapIcon: text('map_icon'),
|
||||||
|
logo: text('logo'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const companiesRelations = relations(companiesTable, ({ many }) => ({
|
||||||
|
projects: many(projectsTable),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export * from './admins.ts';
|
||||||
|
export * from './articles.ts';
|
||||||
|
export * from './projects.ts';
|
||||||
|
export * from './tokens.ts';
|
||||||
|
export * from './companies.ts';
|
||||||
|
export * from './stories.ts';
|
||||||
|
export * from './mapVideos.ts';
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { pgTable, text, uuid, varchar } from 'drizzle-orm/pg-core';
|
||||||
|
import { companiesTable } from './companies';
|
||||||
|
import { relations } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const mapVideosTable = pgTable('map_videos', {
|
||||||
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
|
city: varchar('city', { length: 50 }).notNull(),
|
||||||
|
video: text('video').notNull(),
|
||||||
|
companyId: uuid('company_id').references(() => companiesTable.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const videosRelations = relations(mapVideosTable, ({ one }) => ({
|
||||||
|
company: one(companiesTable, {
|
||||||
|
fields: [mapVideosTable.companyId],
|
||||||
|
references: [companiesTable.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
date,
|
||||||
|
integer,
|
||||||
|
pgTable,
|
||||||
|
uuid,
|
||||||
|
text,
|
||||||
|
varchar,
|
||||||
|
} from 'drizzle-orm/pg-core';
|
||||||
|
import { companiesTable } from './companies';
|
||||||
|
import { relations } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const projectsTable = pgTable('projects', {
|
||||||
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
|
title: varchar('title', { length: 50 }).notNull().unique(),
|
||||||
|
description: varchar('description', { length: 100 }).notNull().default(''),
|
||||||
|
city: varchar('city', { length: 50 }).notNull(),
|
||||||
|
image: text('image').notNull(),
|
||||||
|
stage: integer('stage').notNull().default(1),
|
||||||
|
releaseDate: date('release_date', { mode: 'date' }).notNull(),
|
||||||
|
tags: varchar('tags').array().notNull(),
|
||||||
|
companyId: uuid('company_id').references(() => companiesTable.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const projectsRelations = relations(projectsTable, ({ one }) => ({
|
||||||
|
company: one(companiesTable, {
|
||||||
|
fields: [projectsTable.companyId],
|
||||||
|
references: [companiesTable.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||||
|
import { pgTable } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
|
export const storiesTable = pgTable('stories', {
|
||||||
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
|
video: text('video').notNull(),
|
||||||
|
text: text('text'),
|
||||||
|
preview: text('preview'),
|
||||||
|
createdAt: timestamp('created_at', {
|
||||||
|
mode: 'date',
|
||||||
|
withTimezone: true,
|
||||||
|
})
|
||||||
|
.notNull()
|
||||||
|
.defaultNow(),
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { pgTable, text, uuid } from 'drizzle-orm/pg-core';
|
||||||
|
import { adminsTable } from './admins.ts';
|
||||||
|
import { relations } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const tokensTable = pgTable('tokens', {
|
||||||
|
id: uuid('id').defaultRandom().primaryKey(),
|
||||||
|
accessToken: text('access_token').unique(),
|
||||||
|
refreshToken: text('refresh_token').unique(),
|
||||||
|
adminId: uuid('user_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => adminsTable.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tokensAdmins = relations(tokensTable, ({ one }) => ({
|
||||||
|
admins: one(adminsTable, {
|
||||||
|
fields: [tokensTable.adminId],
|
||||||
|
references: [adminsTable.id],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
+37
-5
@@ -1,7 +1,39 @@
|
|||||||
import { Elysia } from "elysia";
|
import Elysia from 'elysia';
|
||||||
|
import {
|
||||||
|
authController,
|
||||||
|
articlesController,
|
||||||
|
projectsController,
|
||||||
|
uploadController,
|
||||||
|
} from './controllers';
|
||||||
|
import { cors } from '@elysiajs/cors';
|
||||||
|
import { companiesController } from './controllers/companies';
|
||||||
|
import { mailController } from './controllers/mail';
|
||||||
|
import { getReionNameController } from './controllers/getRegionName';
|
||||||
|
import { storiesController } from './controllers/stories';
|
||||||
|
import { mapVideosController } from './controllers/mapVideos';
|
||||||
|
import { db } from './db';
|
||||||
|
import { projectsTable } from './db/schema';
|
||||||
|
import { eq, ne } from 'drizzle-orm';
|
||||||
|
|
||||||
const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
|
try {
|
||||||
|
const app = new Elysia();
|
||||||
|
|
||||||
console.log(
|
app
|
||||||
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
|
.use(
|
||||||
);
|
cors({
|
||||||
|
origin: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.use(authController)
|
||||||
|
.use(projectsController)
|
||||||
|
.use(articlesController)
|
||||||
|
.use(uploadController)
|
||||||
|
.use(companiesController)
|
||||||
|
.use(storiesController)
|
||||||
|
.use(mapVideosController)
|
||||||
|
.use(mailController)
|
||||||
|
.use(getReionNameController)
|
||||||
|
.listen(process.env.PORT);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import Elysia, { Context, error } from 'elysia';
|
||||||
|
import { db } from '../db';
|
||||||
|
import { verifyToken } from '../utils/verifyToken';
|
||||||
|
import { adminsTable } from '../db/schema';
|
||||||
|
|
||||||
|
export const authMiddleware = new Elysia()
|
||||||
|
.derive({ as: 'scoped' }, async ({ headers, cookie, path }: Context) => {
|
||||||
|
const token =
|
||||||
|
(path === '/auth/refresh'
|
||||||
|
? cookie.refreshToken.value
|
||||||
|
: cookie.accessToken.value) || headers.authorization;
|
||||||
|
|
||||||
|
const payload = await verifyToken(
|
||||||
|
path === '/auth/refresh' ? 'refresh' : 'access',
|
||||||
|
token
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!token || !payload)
|
||||||
|
return path === '/auth/check'
|
||||||
|
? { adminId: '' }
|
||||||
|
: error(401, { error: 'Not authorized' });
|
||||||
|
|
||||||
|
const { adminId } = payload;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await db.query.adminsTable.findFirst({
|
||||||
|
where: eq(adminsTable.id, adminId),
|
||||||
|
columns: { hashedPassword: false },
|
||||||
|
with: { tokens: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
!user ||
|
||||||
|
user.tokens.every(
|
||||||
|
({ accessToken, refreshToken }) =>
|
||||||
|
token !== (path === '/auth/refresh' ? refreshToken : accessToken)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.log("attempt to login with someone else's token");
|
||||||
|
return error(401, { error: 'Not authorized' });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, {
|
||||||
|
error: 'Something went wrong (Postgres select)',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
adminId,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.onError({ as: 'scoped' }, ({ error }) => error);
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { adminsTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getUserByUsername(username: string) {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
try {
|
||||||
|
user = await db.query.adminsTable.findFirst({
|
||||||
|
where: eq(adminsTable.username, username),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) return error(404, { error: 'User not found' });
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres select)' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { error } from 'elysia';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { articlesTable } from '../../db/schema';
|
||||||
|
import { slugify } from 'transliteration';
|
||||||
|
|
||||||
|
export async function create(input: typeof articlesTable.$inferInsert) {
|
||||||
|
try {
|
||||||
|
const res = await db
|
||||||
|
.insert(articlesTable)
|
||||||
|
.values({ ...input, slug: !input.drafted ? slugify(input.title) : null })
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!res.length) return error(400, { error: 'Article not created' });
|
||||||
|
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres insert)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { and, arrayContains, not } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { articlesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getAll(tags: string[] = [], offset = 0, limit = 10) {
|
||||||
|
try {
|
||||||
|
return await db.query.articlesTable.findMany({
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
where: and(
|
||||||
|
not(articlesTable.drafted),
|
||||||
|
tags.length > 0 ? arrayContains(articlesTable.tags, tags) : undefined
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres select)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { arrayContained, count } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { articlesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getCount(tags: string[] = []) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
await db
|
||||||
|
.select({ count: count() })
|
||||||
|
.from(articlesTable)
|
||||||
|
.where(
|
||||||
|
tags.length > 0 ? arrayContained(articlesTable.tags, tags) : undefined
|
||||||
|
)
|
||||||
|
)[0].count;
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres select)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { and, arrayContains } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { articlesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getDrafted(
|
||||||
|
tags: string[] = [],
|
||||||
|
offset: number,
|
||||||
|
limit: number
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await db.query.articlesTable.findMany({
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
where: and(
|
||||||
|
articlesTable.drafted,
|
||||||
|
tags.length > 0 ? arrayContains(articlesTable.tags, tags) : undefined
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { articlesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getOne(slug: string) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
(await db.query.articlesTable.findFirst({
|
||||||
|
where: eq(articlesTable.slug, slug),
|
||||||
|
})) ?? error(404, { error: 'Article not found' })
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres select)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './getAll';
|
||||||
|
export * from './getOne';
|
||||||
|
export * from './create';
|
||||||
|
export * from './remove';
|
||||||
|
export * from './update';
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { articlesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function remove(id: string) {
|
||||||
|
try {
|
||||||
|
const article = await db.query.articlesTable.findFirst({
|
||||||
|
where: eq(articlesTable.id, id),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return error(404, { error: 'Article not found' });
|
||||||
|
|
||||||
|
const res = await db
|
||||||
|
.delete(articlesTable)
|
||||||
|
.where(eq(articlesTable.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!res.length) return error(400, { error: 'Article not deleted' });
|
||||||
|
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres delete)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { articlesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
import { slugify } from 'transliteration';
|
||||||
|
|
||||||
|
export async function update(
|
||||||
|
id: string,
|
||||||
|
input: typeof articlesTable.$inferInsert
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const article = await db.query.articlesTable.findFirst({
|
||||||
|
where: eq(articlesTable.id, id),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!article) return error(404, { error: 'Article not found' });
|
||||||
|
|
||||||
|
const res = await db
|
||||||
|
.update(articlesTable)
|
||||||
|
.set({ ...input, slug: !input.drafted ? slugify(input.title) : null })
|
||||||
|
.where(eq(articlesTable.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!res.length) return error(400, { error: 'Article not updated' });
|
||||||
|
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres update)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Cookie, error } from 'elysia';
|
||||||
|
import { generateToken } from '../../utils/generateToken';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { tokensTable } from '../../db/schema';
|
||||||
|
|
||||||
|
export async function generateTokens(
|
||||||
|
adminId: string,
|
||||||
|
cookie: Record<string, Cookie<string | undefined>>
|
||||||
|
) {
|
||||||
|
const accessToken = await generateToken(adminId, 'access');
|
||||||
|
|
||||||
|
const refreshToken = await generateToken(adminId, 'refresh');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await db
|
||||||
|
.insert(tokensTable)
|
||||||
|
.values({ adminId, accessToken, refreshToken })
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!result.length) return error(500, { error: 'Cannot add new user' });
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres insert)' });
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie.accessToken.set({
|
||||||
|
value: accessToken,
|
||||||
|
httpOnly: true,
|
||||||
|
// secure: true,
|
||||||
|
maxAge: 3600 * 24 * 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
cookie.refreshToken.set({
|
||||||
|
value: refreshToken,
|
||||||
|
httpOnly: true,
|
||||||
|
// secure: true,
|
||||||
|
maxAge: 3600 * 24 * 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './login';
|
||||||
|
export * from './logout';
|
||||||
|
export * from './refresh';
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Cookie } from 'elysia';
|
||||||
|
import { ElysiaCustomStatusResponse, error } from 'elysia/error';
|
||||||
|
import { getUserByUsername } from '../admins/getByUsername';
|
||||||
|
import { generateTokens } from './generateTokens';
|
||||||
|
|
||||||
|
export async function login(
|
||||||
|
body: { username: string; password: string },
|
||||||
|
cookie: Record<string, Cookie<string | undefined>>
|
||||||
|
) {
|
||||||
|
const { username, password } = body;
|
||||||
|
|
||||||
|
const user = await getUserByUsername(username);
|
||||||
|
|
||||||
|
if (user instanceof ElysiaCustomStatusResponse)
|
||||||
|
return error(user.code, user.response.error);
|
||||||
|
|
||||||
|
const passwordMatches = user
|
||||||
|
? Bun.password.verifySync(password, user.hashedPassword)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
if (!user || !passwordMatches)
|
||||||
|
return error(401, { error: 'Wrong credentials' });
|
||||||
|
|
||||||
|
return await generateTokens(user.id, cookie);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Cookie, error } from 'elysia';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { tokensTable } from '../../db/schema';
|
||||||
|
import { and, eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function logout(
|
||||||
|
cookie: Record<string, Cookie<string | undefined>> & {
|
||||||
|
accessToken: Cookie<string>;
|
||||||
|
refreshToken: Cookie<string>;
|
||||||
|
},
|
||||||
|
adminId: string
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const result = await db
|
||||||
|
.delete(tokensTable)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(tokensTable.adminId, adminId),
|
||||||
|
eq(tokensTable.accessToken, cookie.accessToken.value!)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (result.length === 0) return error(400, { error: 'Cannot logout user' });
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, {
|
||||||
|
error: 'Something went wrong (Postgres delete)',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie.accessToken.remove();
|
||||||
|
cookie.refreshToken.remove();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Cookie, error } from 'elysia';
|
||||||
|
import { generateToken } from '../../utils/generateToken';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { tokensTable } from '../../db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function refresh(
|
||||||
|
cookie: Record<string, Cookie<string | undefined>>,
|
||||||
|
userId: string
|
||||||
|
) {
|
||||||
|
const accessToken = await generateToken(userId, 'access');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.update(tokensTable)
|
||||||
|
.set({ accessToken })
|
||||||
|
.where(eq(tokensTable.accessToken, cookie.accessToken.value!));
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres update)' });
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie.accessToken.set({
|
||||||
|
value: accessToken,
|
||||||
|
httpOnly: true,
|
||||||
|
// secure: true,
|
||||||
|
maxAge: 3600 * 24 * 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { accessToken };
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { error } from 'elysia';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { companiesTable } from '../../db/schema';
|
||||||
|
|
||||||
|
export async function create(body: typeof companiesTable.$inferInsert) {
|
||||||
|
try {
|
||||||
|
return (await db.insert(companiesTable).values(body).returning())[0];
|
||||||
|
} catch (err) {
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres insert)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { companiesTable, projectsTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getByCity(city: string) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
await db
|
||||||
|
.selectDistinctOn([projectsTable.companyId])
|
||||||
|
.from(projectsTable)
|
||||||
|
.where(eq(projectsTable.city, city))
|
||||||
|
.innerJoin(
|
||||||
|
companiesTable,
|
||||||
|
eq(projectsTable.companyId, companiesTable.id)
|
||||||
|
)
|
||||||
|
).map(({ companies }) => companies);
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { count, eq, getTableColumns } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { companiesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getCount(city?: string) {
|
||||||
|
try {
|
||||||
|
// const res = await db
|
||||||
|
// .select()
|
||||||
|
// .from(projectsTable)
|
||||||
|
// .where(city ? eq(projectsTable.city, city) : undefined)
|
||||||
|
// .groupBy(projectsTable.companyId, projectsTable.id);
|
||||||
|
// console.log('res', res);
|
||||||
|
return (
|
||||||
|
await db.select({ count: count() }).from(companiesTable).limit(1)
|
||||||
|
)[0].count;
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres select)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { error } from 'elysia';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { companiesTable, projectsTable } from '../../db/schema';
|
||||||
|
import { count, desc, eq, getTableColumns } from 'drizzle-orm';
|
||||||
|
import { aggregateOneToMany } from '../../utils/aggregateOneToMany';
|
||||||
|
|
||||||
|
export async function getMany(city?: string) {
|
||||||
|
try {
|
||||||
|
const res = await db
|
||||||
|
.select({ ...companiesTable, projects: projectsTable } as ReturnType<
|
||||||
|
typeof getTableColumns<typeof companiesTable>
|
||||||
|
> & {
|
||||||
|
projects: ReturnType<typeof getTableColumns<typeof projectsTable>>;
|
||||||
|
})
|
||||||
|
.from(companiesTable)
|
||||||
|
.orderBy(
|
||||||
|
desc(
|
||||||
|
db
|
||||||
|
.select({ count: count() })
|
||||||
|
.from(projectsTable)
|
||||||
|
.where(eq(companiesTable.id, projectsTable.companyId))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.leftJoin(projectsTable, eq(companiesTable.id, projectsTable.companyId));
|
||||||
|
|
||||||
|
return aggregateOneToMany(res, 'projects');
|
||||||
|
|
||||||
|
// or
|
||||||
|
|
||||||
|
// return await db.query.companiesTable.findMany({
|
||||||
|
// with: { projects: true },
|
||||||
|
// where: title ? eq(companiesTable.title, title) : undefined,
|
||||||
|
// orderBy: (companiesTable, { desc }) => [
|
||||||
|
// desc(
|
||||||
|
// db
|
||||||
|
// .select({ count: count() })
|
||||||
|
// .from(projectsTable)
|
||||||
|
// .where(eq(projectsTable.companyId, companiesTable.id))
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres select)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './getMany';
|
||||||
|
export * from './getCount';
|
||||||
|
export * from './create';
|
||||||
|
export * from './update';
|
||||||
|
export * from './remove';
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { companiesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function remove(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await db
|
||||||
|
.delete(companiesTable)
|
||||||
|
.where(eq(companiesTable.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!res.length) return error(404, 'Not Found');
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { companiesTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function update(
|
||||||
|
id: string,
|
||||||
|
data: typeof companiesTable.$inferInsert
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const res = await db
|
||||||
|
.update(companiesTable)
|
||||||
|
.set(data)
|
||||||
|
.where(eq(companiesTable.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!res.length) return error(404, { error: 'Projects not found' });
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { error } from 'elysia';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { mapVideosTable } from '../../db/schema';
|
||||||
|
|
||||||
|
export async function createMapVideo(
|
||||||
|
paylaod: typeof mapVideosTable.$inferInsert
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const res = await db.insert(mapVideosTable).values(paylaod).returning();
|
||||||
|
if (!res) return error(500, 'Internal Server Error');
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { mapVideosTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function deleteMapVideo(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await db
|
||||||
|
.delete(mapVideosTable)
|
||||||
|
.where(eq(mapVideosTable.id, id))
|
||||||
|
.returning();
|
||||||
|
if (!res.length) return error(404, 'Not Found');
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { error } from 'elysia';
|
||||||
|
import { db } from '../../db';
|
||||||
|
|
||||||
|
export async function getAll() {
|
||||||
|
try {
|
||||||
|
return await db.query.mapVideosTable.findMany({ with: { company: true } });
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { error } from 'elysia';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { mapVideosTable } from '../../db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export async function updateMapVideo(
|
||||||
|
id: string,
|
||||||
|
payload: typeof mapVideosTable.$inferInsert
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const candidate = await db.query.mapVideosTable.findFirst();
|
||||||
|
if (!candidate) return error(404, 'Not Found');
|
||||||
|
const res = await db
|
||||||
|
.update(mapVideosTable)
|
||||||
|
.set(payload)
|
||||||
|
.where(eq(mapVideosTable.id, id))
|
||||||
|
.returning();
|
||||||
|
if (!res) return error(500, 'Internal Server Error');
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, 'Internal Server Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { error } from 'elysia';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { projectsTable } from '../../db/schema';
|
||||||
|
|
||||||
|
export async function create(project: typeof projectsTable.$inferInsert) {
|
||||||
|
try {
|
||||||
|
const res = await db.insert(projectsTable).values(project).returning();
|
||||||
|
|
||||||
|
if (res.length === 0) return error(422, { error: 'Unprocessable Content' });
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { and, arrayContains, count, eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { projectsTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getCount(tags: string[] = [], city?: string) {
|
||||||
|
let res;
|
||||||
|
|
||||||
|
try {
|
||||||
|
res = await db
|
||||||
|
.select({ count: count() })
|
||||||
|
.from(projectsTable)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
tags.length > 0 ? arrayContains(projectsTable.tags, tags) : undefined,
|
||||||
|
city ? eq(projectsTable.city, city) : undefined
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return res[0].count;
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
|
||||||
|
return error(500, { error: 'Something went wrong' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { and, arrayContains, desc, eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { projectsTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getMany(
|
||||||
|
tags: string[] = [],
|
||||||
|
city?: string,
|
||||||
|
limit?: number,
|
||||||
|
companyId?: string
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return await db.query.projectsTable.findMany({
|
||||||
|
where: and(
|
||||||
|
tags.length > 0 ? arrayContains(projectsTable.tags, tags) : undefined,
|
||||||
|
city ? eq(projectsTable.city, city) : undefined,
|
||||||
|
companyId ? eq(projectsTable.companyId, companyId) : undefined
|
||||||
|
),
|
||||||
|
with: { company: true },
|
||||||
|
orderBy: desc(projectsTable.releaseDate),
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { projectsTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function getOne(id: string) {
|
||||||
|
let project;
|
||||||
|
|
||||||
|
try {
|
||||||
|
project = await db.query.projectsTable.findFirst({
|
||||||
|
where: eq(projectsTable.id, id),
|
||||||
|
with: { company: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!project) return error(404, { error: 'Not found' });
|
||||||
|
|
||||||
|
return project;
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres select)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export * from './create';
|
||||||
|
export * from './getCount';
|
||||||
|
export * from './getMany';
|
||||||
|
export * from './getOne';
|
||||||
|
export * from './remove';
|
||||||
|
export * from './update';
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { projectsTable } from '../../db/schema';
|
||||||
|
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
|
||||||
|
import { s3client } from '../../config/s3client';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function remove(id: string) {
|
||||||
|
try {
|
||||||
|
const res = await db
|
||||||
|
.delete(projectsTable)
|
||||||
|
.where(eq(projectsTable.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!res.length) return error(404, { error: 'Project not found' });
|
||||||
|
|
||||||
|
const project = res[0];
|
||||||
|
|
||||||
|
await s3client.send(
|
||||||
|
new DeleteObjectCommand({
|
||||||
|
Bucket: process.env.S3_BUCKET!,
|
||||||
|
Key: project.image,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return project;
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Something went wrong (Postgres delete)' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { db } from '../../db';
|
||||||
|
import { projectsTable } from '../../db/schema';
|
||||||
|
import { error } from 'elysia';
|
||||||
|
|
||||||
|
export async function update(
|
||||||
|
id: string,
|
||||||
|
data: typeof projectsTable.$inferInsert
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const res = await db
|
||||||
|
.update(projectsTable)
|
||||||
|
.set(data)
|
||||||
|
.where(eq(projectsTable.id, id))
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!res.length) return error(404, { error: 'Project not found' });
|
||||||
|
return res[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.log((err as Error).message);
|
||||||
|
return error(500, { error: 'Internal Server Error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
export interface IContent {
|
||||||
|
type: 'Content';
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IImage {
|
||||||
|
img: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISlider {
|
||||||
|
type: 'Slider';
|
||||||
|
images: IImage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVideo {
|
||||||
|
type: 'Video';
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IQuote {
|
||||||
|
type: 'Quote';
|
||||||
|
avatar: string;
|
||||||
|
name: string;
|
||||||
|
position: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IButtonLink {
|
||||||
|
type: 'ButtonLink';
|
||||||
|
title: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Block = IContent | ISlider | IVideo | IQuote | IButtonLink;
|
||||||
|
|
||||||
|
export interface IArticle {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
createdAt: Date;
|
||||||
|
cardImage: string;
|
||||||
|
blocks: Block[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
export function aggregateOneToMany<
|
||||||
|
TOne extends { id: string },
|
||||||
|
TMany extends { id: string },
|
||||||
|
TManyKey extends keyof TOne
|
||||||
|
>(
|
||||||
|
joined: (TOne & { [manyKey in TManyKey]: TMany | null })[],
|
||||||
|
manyKey: TManyKey
|
||||||
|
) {
|
||||||
|
return joined.reduce<(TOne & { [key in TManyKey]: TMany[] })[]>(
|
||||||
|
(acc, row) => {
|
||||||
|
if (!acc.some(({ id }) => id === row.id))
|
||||||
|
acc.push({ ...row, [manyKey]: [] });
|
||||||
|
if (row[manyKey])
|
||||||
|
acc.find(({ id }) => id === row.id)?.[manyKey].push(row[manyKey]);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [{ id:string, ..., <manyKey>: [{ id:string, ... }] }]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { SignJWT } from 'jose';
|
||||||
|
|
||||||
|
export async function generateToken(
|
||||||
|
adminId: string,
|
||||||
|
type: 'access' | 'refresh'
|
||||||
|
) {
|
||||||
|
return await new SignJWT({
|
||||||
|
adminId,
|
||||||
|
})
|
||||||
|
.setProtectedHeader({ alg: 'HS256' })
|
||||||
|
.setExpirationTime(
|
||||||
|
type === 'access'
|
||||||
|
? process.env.JWT_ACCESS_EXP_TIME
|
||||||
|
: process.env.JWT_REFRESH_EXP_TIME
|
||||||
|
)
|
||||||
|
.sign(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
type === 'access'
|
||||||
|
? process.env.JWT_ACCESS_SECRET
|
||||||
|
: process.env.JWT_REFRESH_SECRET
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { jwtVerify } from 'jose';
|
||||||
|
|
||||||
|
export async function verifyToken(type: 'access' | 'refresh', token?: string) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
await jwtVerify<{ adminId: string }>(
|
||||||
|
token ?? '',
|
||||||
|
new TextEncoder().encode(
|
||||||
|
type === 'access'
|
||||||
|
? process.env.JWT_ACCESS_SECRET
|
||||||
|
: process.env.JWT_REFRESH_SECRET
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).payload;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-10
@@ -11,7 +11,7 @@
|
|||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
"target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
@@ -25,14 +25,16 @@
|
|||||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
/* Modules */
|
/* Modules */
|
||||||
"module": "ES2022", /* Specify what module code is generated. */
|
"module": "NodeNext" /* Specify what module code is generated. */,
|
||||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
"moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
"types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */
|
"types": [
|
||||||
|
"bun-types"
|
||||||
|
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
@@ -51,7 +53,7 @@
|
|||||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||||
// "removeComments": true, /* Disable emitting comments. */
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
"noEmit": true /* Disable emitting files from a compilation. */,
|
||||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
@@ -71,12 +73,13 @@
|
|||||||
/* Interop Constraints */
|
/* Interop Constraints */
|
||||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
|
||||||
/* Type Checking */
|
/* Type Checking */
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
@@ -98,6 +101,14 @@
|
|||||||
|
|
||||||
/* Completeness */
|
/* Completeness */
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"./drizzle.config.ts",
|
||||||
|
"./env.d.ts",
|
||||||
|
"./src/**/*.ts",
|
||||||
|
"./bun.config.ts"
|
||||||
|
// "src/controllers/getRegionName.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user