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:
2024-11-21 10:34:18 +02:00
committed by GitHub
parent cb8c3a1697
commit 132f526179
10 changed files with 1882 additions and 19 deletions

4
.gitignore vendored
View File

@@ -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**

View File

@@ -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) => {

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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"
}
}

View 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
}

View 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
}

View File

@@ -0,0 +1,9 @@
terraform {
backend "s3" {
bucket = "tasker-tf-infra"
key = "terraform.tfstate"
region = "eu-north-1"
}
}
provider "aws" {}

View 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
}

View 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"]
}