This commit is contained in:
2025-02-27 10:40:29 +05:00
parent 3d3e5b2cdb
commit f2de9827ff
67 changed files with 1749 additions and 19 deletions
+16
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
{
"postman.settings.dotenv-detection-notification-visibility": false
}
+8
View File
@@ -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('\\'),
});
BIN
View File
Binary file not shown.
+10
View File
@@ -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!,
},
});
Vendored
+20
View File
@@ -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;
}
}
+18 -3
View File
@@ -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"
} }
+10
View File
@@ -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,
},
});
+116
View File
@@ -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({}),
},
}
);
+41
View File
@@ -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)
);
+63
View File
@@ -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({}),
},
});
+17
View File
@@ -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' }) }),
}
);
+4
View File
@@ -0,0 +1,4 @@
export * from './articles';
export * from './auth';
export * from './projects';
export * from './upload';
+51
View File
@@ -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()),
}),
}
);
+49
View File
@@ -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({}),
},
});
+105
View File
@@ -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({}),
},
});
+123
View File
@@ -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({}),
},
}
);
+43
View File
@@ -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({}),
},
}
);
+7
View File
@@ -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 });
+13
View File
@@ -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),
}));
+20
View File
@@ -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(),
});
+15
View File
@@ -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),
}));
+7
View File
@@ -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';
+17
View File
@@ -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],
}),
}));
+29
View File
@@ -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],
}),
}));
+15
View File
@@ -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(),
});
+19
View File
@@ -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
View File
@@ -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);
}
+54
View File
@@ -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);
+21
View File
@@ -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;
}
+20
View File
@@ -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)' });
}
}
+20
View File
@@ -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)' });
}
}
+20
View File
@@ -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)' });
}
}
+24
View File
@@ -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' });
}
}
+17
View File
@@ -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)' });
}
}
+5
View File
@@ -0,0 +1,5 @@
export * from './getAll';
export * from './getOne';
export * from './create';
export * from './remove';
export * from './update';
+26
View File
@@ -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)' });
}
}
+31
View File
@@ -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)' });
}
}
+44
View File
@@ -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,
};
}
+3
View File
@@ -0,0 +1,3 @@
export * from './login';
export * from './logout';
export * from './refresh';
+25
View File
@@ -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);
}
+38
View File
@@ -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,
};
}
+31
View File
@@ -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 };
}
+11
View File
@@ -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)' });
}
}
+22
View File
@@ -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' });
}
}
+21
View File
@@ -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)' });
}
}
+46
View File
@@ -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)' });
}
}
+5
View File
@@ -0,0 +1,5 @@
export * from './getMany';
export * from './getCount';
export * from './create';
export * from './update';
export * from './remove';
+19
View File
@@ -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');
}
}
+23
View File
@@ -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' });
}
}
+16
View File
@@ -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');
}
}
+18
View File
@@ -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');
}
}
+11
View File
@@ -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' });
}
}
+24
View File
@@ -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');
}
}
+15
View File
@@ -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' });
}
}
+27
View File
@@ -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' });
}
}
+27
View File
@@ -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' });
}
}
+22
View File
@@ -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)' });
}
}
+6
View File
@@ -0,0 +1,6 @@
export * from './create';
export * from './getCount';
export * from './getMany';
export * from './getOne';
export * from './remove';
export * from './update';
+30
View File
@@ -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)' });
}
}
+23
View File
@@ -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' });
}
}
+44
View File
@@ -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[];
}
+21
View File
@@ -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, ... }] }]
+23
View File
@@ -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
)
);
}
View File
+18
View File
@@ -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
View File
@@ -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"]
} }