feat: Add Terraform configuration for AWS (#2)
* feat: Add Terraform configuration for AWS * feat: Update data models to use string identifiers and add DynamoDB table configurations * feat: Update Terraform S3 backend configuration
This commit was merged in pull request #2.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,7 +7,6 @@
|
|||||||
**/.next/
|
**/.next/
|
||||||
**/out/
|
**/out/
|
||||||
|
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/*.pem
|
**/*.pem
|
||||||
@@ -24,3 +23,6 @@
|
|||||||
|
|
||||||
# images
|
# images
|
||||||
**/public/**
|
**/public/**
|
||||||
|
|
||||||
|
# terraform
|
||||||
|
**/**/.terraform**
|
||||||
|
|||||||
@@ -41,6 +41,20 @@ const formFields = {
|
|||||||
inputProps: { type: "password", required: true },
|
inputProps: { type: "password", required: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
signIn: {
|
||||||
|
username: {
|
||||||
|
order: 1,
|
||||||
|
placeholder: "Enter your username or email",
|
||||||
|
label: "Username or Email",
|
||||||
|
inputProps: { required: true },
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
order: 2,
|
||||||
|
placeholder: "Enter your password",
|
||||||
|
label: "Password",
|
||||||
|
inputProps: { type: "password", required: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const AuthProvider = ({ children }: any) => {
|
const AuthProvider = ({ children }: any) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
|||||||
import { fetchAuthSession, getCurrentUser } from "aws-amplify/auth";
|
import { fetchAuthSession, getCurrentUser } from "aws-amplify/auth";
|
||||||
|
|
||||||
export interface Project {
|
export interface Project {
|
||||||
id: number;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
startDate?: string;
|
startDate?: string;
|
||||||
@@ -26,24 +26,24 @@ export enum Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
userId?: number;
|
userId?: string;
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
profilePictureUrl?: string;
|
profilePictureUrl?: string;
|
||||||
cognitoId?: string;
|
cognitoId?: string;
|
||||||
teamId?: number;
|
teamId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Attachment {
|
export interface Attachment {
|
||||||
id: number;
|
attachmentId: string;
|
||||||
fileURL: string;
|
fileURL: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
taskId: number;
|
taskId: string;
|
||||||
uploadedById: number;
|
uploadedById: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: number;
|
taskId: string;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
status?: Status;
|
status?: Status;
|
||||||
@@ -52,9 +52,9 @@ export interface Task {
|
|||||||
startDate?: string;
|
startDate?: string;
|
||||||
dueDate?: string;
|
dueDate?: string;
|
||||||
points?: number;
|
points?: number;
|
||||||
projectId: number;
|
projectId: string;
|
||||||
authorUserId?: number;
|
authorUserId?: string;
|
||||||
assignedUserId?: number;
|
assignedUserId?: string;
|
||||||
|
|
||||||
author?: User;
|
author?: User;
|
||||||
assignee?: User;
|
assignee?: User;
|
||||||
@@ -69,10 +69,10 @@ export interface SearchResults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Team {
|
export interface Team {
|
||||||
teamId: number;
|
teamId: string;
|
||||||
teamName: string;
|
teamName: string;
|
||||||
productOwnerUserId?: number;
|
productOwnerUserId?: string;
|
||||||
projectManagerUserId?: number;
|
projectManagerUserId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const api = createApi({
|
export const api = createApi({
|
||||||
@@ -124,15 +124,15 @@ export const api = createApi({
|
|||||||
query: ({ projectId }) => `tasks?projectId=${projectId}`,
|
query: ({ projectId }) => `tasks?projectId=${projectId}`,
|
||||||
providesTags: (result) =>
|
providesTags: (result) =>
|
||||||
result
|
result
|
||||||
? result.map(({ id }) => ({ type: "Tasks" as const, id }))
|
? result.map(({ taskId }) => ({ type: "Tasks" as const, taskId }))
|
||||||
: [{ type: "Tasks" as const }],
|
: [{ type: "Tasks" as const }],
|
||||||
}),
|
}),
|
||||||
getTasksByUser: build.query<Task[], number>({
|
getTasksByUser: build.query<Task[], string>({
|
||||||
query: (userId) => `tasks/user/${userId}`,
|
query: (userId) => `tasks/user/${userId}`,
|
||||||
providesTags: (result, error, userId) =>
|
providesTags: (result, error, userId) =>
|
||||||
result
|
result
|
||||||
? result.map(({ id }) => ({ type: "Tasks", id }))
|
? result.map(({ taskId }) => ({ type: "Tasks", taskId }))
|
||||||
: [{ type: "Tasks", id: userId }],
|
: [{ type: "Tasks", taskId: userId }],
|
||||||
}),
|
}),
|
||||||
createTask: build.mutation<Task, Partial<Task>>({
|
createTask: build.mutation<Task, Partial<Task>>({
|
||||||
query: (task) => ({
|
query: (task) => ({
|
||||||
@@ -142,7 +142,7 @@ export const api = createApi({
|
|||||||
}),
|
}),
|
||||||
invalidatesTags: ["Tasks"],
|
invalidatesTags: ["Tasks"],
|
||||||
}),
|
}),
|
||||||
updateTaskStatus: build.mutation<Task, { taskId: number; status: string }>({
|
updateTaskStatus: build.mutation<Task, { taskId: string; status: string }>({
|
||||||
query: ({ taskId, status }) => ({
|
query: ({ taskId, status }) => ({
|
||||||
url: `tasks/${taskId}/status`,
|
url: `tasks/${taskId}/status`,
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
|||||||
1448
tasker-server/package-lock.json
generated
Normal file
1448
tasker-server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
tasker-server/package.json
Normal file
22
tasker-server/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "tasker-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"infra:init": "cd terraform && AWS_PROFILE=default terraform init",
|
||||||
|
"infra:plan": "cd terraform && AWS_PROFILE=default terraform plan",
|
||||||
|
"infra:apply": "cd terraform && AWS_PROFILE=default terraform apply"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"aws-sdk": "^2.1692.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.9.1",
|
||||||
|
"eslint": "^9.15.0",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
91
tasker-server/terraform/cognito.tf
Normal file
91
tasker-server/terraform/cognito.tf
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
resource "aws_cognito_user_pool" "tasker_cognito_user_pool" {
|
||||||
|
name = "tasker-cognito-user-pool"
|
||||||
|
|
||||||
|
# Sign-in options
|
||||||
|
alias_attributes = ["preferred_username", "email"]
|
||||||
|
|
||||||
|
# User name requirements
|
||||||
|
username_configuration {
|
||||||
|
case_sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password policy
|
||||||
|
password_policy {
|
||||||
|
minimum_length = 8
|
||||||
|
require_uppercase = false
|
||||||
|
require_lowercase = false
|
||||||
|
require_numbers = false
|
||||||
|
require_symbols = false
|
||||||
|
}
|
||||||
|
|
||||||
|
# MFA settings
|
||||||
|
mfa_configuration = "OFF"
|
||||||
|
|
||||||
|
# Account recovery
|
||||||
|
account_recovery_setting {
|
||||||
|
recovery_mechanism {
|
||||||
|
name = "verified_email"
|
||||||
|
priority = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Auto-verified attributes
|
||||||
|
auto_verified_attributes = ["email"]
|
||||||
|
|
||||||
|
# Self-registration and message delivery
|
||||||
|
admin_create_user_config {
|
||||||
|
allow_admin_create_user_only = false # Enable self-registration
|
||||||
|
}
|
||||||
|
|
||||||
|
verification_message_template {
|
||||||
|
default_email_option = "CONFIRM_WITH_CODE"
|
||||||
|
email_subject = "Tasker - Verify your email address"
|
||||||
|
email_message = "Your verification code is {####}."
|
||||||
|
}
|
||||||
|
|
||||||
|
user_attribute_update_settings {
|
||||||
|
attributes_require_verification_before_update = ["email"]
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Environment = "Dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cognito_user_pool_client" "tasker_cognito_client" {
|
||||||
|
name = "tasker-cognito-client"
|
||||||
|
user_pool_id = aws_cognito_user_pool.tasker_cognito_user_pool.id
|
||||||
|
generate_secret = false
|
||||||
|
|
||||||
|
allowed_oauth_flows = []
|
||||||
|
allowed_oauth_scopes = []
|
||||||
|
supported_identity_providers = ["COGNITO"]
|
||||||
|
|
||||||
|
prevent_user_existence_errors = "ENABLED"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_cognito_user_pool_domain" "tasker_cognito_domain" {
|
||||||
|
domain = "tasker"
|
||||||
|
user_pool_id = aws_cognito_user_pool.tasker_cognito_user_pool.id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "user_pool_id" {
|
||||||
|
name = "/tasker/cognito/user-pool-id"
|
||||||
|
description = "Tasker Cognito User Pool ID"
|
||||||
|
type = "String"
|
||||||
|
value = aws_cognito_user_pool.tasker_cognito_user_pool.id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "client_id" {
|
||||||
|
name = "/tasker/cognito/client-id"
|
||||||
|
description = "Tasker Cognito Client ID"
|
||||||
|
type = "String"
|
||||||
|
value = aws_cognito_user_pool_client.tasker_cognito_client.id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "cognito_domain" {
|
||||||
|
name = "/tasker/cognito/domain"
|
||||||
|
description = "Tasker Cognito Domain"
|
||||||
|
type = "String"
|
||||||
|
value = aws_cognito_user_pool_domain.tasker_cognito_domain.domain
|
||||||
|
}
|
||||||
204
tasker-server/terraform/dynamodb.tf
Normal file
204
tasker-server/terraform/dynamodb.tf
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
resource "aws_dynamodb_table" "tasker_project_table" {
|
||||||
|
name = "tasker-project-table"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "projectId"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "type"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "projectId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_project_table_name" {
|
||||||
|
name = "/tasker/dynamodb/project-table-name"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_project_table.name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_project_table_arn" {
|
||||||
|
name = "/tasker/dynamodb/project-table-arn"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_project_table.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_dynamodb_table" "tasker_user_table" {
|
||||||
|
name = "tasker-user-table"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "cognitoId"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "type"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "cognitoId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "userId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
global_secondary_index {
|
||||||
|
name = "GSI-user-id"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "userId"
|
||||||
|
projection_type = "ALL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_user_table_name" {
|
||||||
|
name = "/tasker/dynamodb/user-table-name"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_user_table.name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_user_table_arn" {
|
||||||
|
name = "/tasker/dynamodb/user-table-arn"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_user_table.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_dynamodb_table" "tasker_team_table" {
|
||||||
|
name = "tasker-team-table"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "teamId"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "type"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "teamId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_team_table_name" {
|
||||||
|
name = "/tasker/dynamodb/team-table-name"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_team_table.name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_team_table_arn" {
|
||||||
|
name = "/tasker/dynamodb/team-table-arn"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_team_table.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_dynamodb_table" "tasker_task_table" {
|
||||||
|
name = "tasker-task-table"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "taskId"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "type"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "taskId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "projectId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "authorUserId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "assignedUserId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
global_secondary_index {
|
||||||
|
name = "GSI-project-id"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "projectId"
|
||||||
|
projection_type = "ALL"
|
||||||
|
}
|
||||||
|
|
||||||
|
global_secondary_index {
|
||||||
|
name = "GSI-author-user-id"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "authorUserId"
|
||||||
|
projection_type = "ALL"
|
||||||
|
}
|
||||||
|
|
||||||
|
global_secondary_index {
|
||||||
|
name = "GSI-assigned-user-id"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "assignedUserId"
|
||||||
|
projection_type = "ALL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_task_table_name" {
|
||||||
|
name = "/tasker/dynamodb/task-table-name"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_task_table.name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_task_table_arn" {
|
||||||
|
name = "/tasker/dynamodb/task-table-arn"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_task_table.arn
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_dynamodb_table" "tasker_task_extra_table" {
|
||||||
|
name = "tasker-task-extra-table"
|
||||||
|
billing_mode = "PAY_PER_REQUEST"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "id"
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "type"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "id"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute {
|
||||||
|
name = "taskId"
|
||||||
|
type = "S"
|
||||||
|
}
|
||||||
|
|
||||||
|
global_secondary_index {
|
||||||
|
name = "GSI-task-id"
|
||||||
|
hash_key = "type"
|
||||||
|
range_key = "taskId"
|
||||||
|
projection_type = "ALL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_task_extra_table_name" {
|
||||||
|
name = "/tasker/dynamodb/task-extra-table-name"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_task_extra_table.name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_task_extra_table_arn" {
|
||||||
|
name = "/tasker/dynamodb/task-extra-table-arn"
|
||||||
|
type = "String"
|
||||||
|
value = aws_dynamodb_table.tasker_task_extra_table.arn
|
||||||
|
}
|
||||||
9
tasker-server/terraform/main.tf
Normal file
9
tasker-server/terraform/main.tf
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
terraform {
|
||||||
|
backend "s3" {
|
||||||
|
bucket = "tasker-tf-infra"
|
||||||
|
key = "terraform.tfstate"
|
||||||
|
region = "eu-north-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "aws" {}
|
||||||
46
tasker-server/terraform/s3.tf
Normal file
46
tasker-server/terraform/s3.tf
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
resource "aws_s3_bucket" "tasker_public_images" {
|
||||||
|
bucket = "tasker-public-images"
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Environment = "Dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_ownership_controls" "tasker_public_images_ownership_controls" {
|
||||||
|
bucket = aws_s3_bucket.tasker_public_images.id
|
||||||
|
rule {
|
||||||
|
object_ownership = "BucketOwnerPreferred"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_public_access_block" "tasker_public_images_public_access_block" {
|
||||||
|
bucket = aws_s3_bucket.tasker_public_images.id
|
||||||
|
|
||||||
|
block_public_acls = false
|
||||||
|
block_public_policy = false
|
||||||
|
ignore_public_acls = false
|
||||||
|
restrict_public_buckets = false
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_s3_bucket_acl" "tasker_public_images_acl" {
|
||||||
|
depends_on = [
|
||||||
|
aws_s3_bucket_ownership_controls.tasker_public_images_ownership_controls,
|
||||||
|
aws_s3_bucket_public_access_block.tasker_public_images_public_access_block,
|
||||||
|
]
|
||||||
|
|
||||||
|
bucket = aws_s3_bucket.tasker_public_images.id
|
||||||
|
acl = "public-read"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_public_images_name" {
|
||||||
|
name = "/tasker/s3/public-images-bucket-name"
|
||||||
|
type = "String"
|
||||||
|
value = aws_s3_bucket.tasker_public_images.bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_ssm_parameter" "tasker_public_images_arn" {
|
||||||
|
name = "/tasker/s3/public-images-bucket-arn"
|
||||||
|
type = "String"
|
||||||
|
value = aws_s3_bucket.tasker_public_images.arn
|
||||||
|
}
|
||||||
27
tasker-server/tsconfig.json
Normal file
27
tasker-server/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user