Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e87534b
user-authentication
rifasania Feb 7, 2025
4b87b16
fixing:user-authentication
rifasania Feb 11, 2025
de17149
implement linter and prettier
rifasania Feb 11, 2025
cd1e3b7
fixing2:user-authentication
rifasania Feb 12, 2025
e52dd01
fixing3:user-authentication
rifasania Feb 12, 2025
fbd294b
Test husky pre-commit
rifasania Feb 13, 2025
0ca604b
test husky pre-commmit
rifasania Feb 13, 2025
22bd248
test husky pre-committt
rifasania Feb 13, 2025
ddb4707
test prettier jebal
rifasania Feb 13, 2025
f3169a7
test linter prettier
rifasania Feb 13, 2025
52c8390
pre-commit linter prettier done
rifasania Feb 13, 2025
0da64bc
pre-commit linter and prettier
rifasania Feb 13, 2025
bff3aa8
cek
rifasania Feb 13, 2025
a3d5f3e
fixed all
rifasania Feb 13, 2025
17bcfc1
fixed error
rifasania Feb 13, 2025
1af47b5
fixed:all
rifasania Feb 13, 2025
8fc9dea
ddl-operation
rifasania Feb 17, 2025
85e5f2a
resolve conflict pt.2
rifasania Feb 18, 2025
dfbc3b3
finished resolve conflict
rifasania Feb 18, 2025
92ce8e5
fixing:ddl-operation
rifasania Feb 19, 2025
0404e2e
fixed ddl operation
rifasania Feb 21, 2025
a8fb6e7
fixing3:ddl-operation
rifasania Feb 24, 2025
5f946f2
fixed code + testing
rifasania Feb 25, 2025
d3bae85
fixed github actions
rifasania Feb 25, 2025
d83a0c4
fixed github action: jwt
rifasania Feb 25, 2025
5ac8582
fixing:github action
rifasania Feb 26, 2025
5e225e3
fixing:github-action:add-sync
rifasania Feb 26, 2025
da489c3
fixing:github-action:fix-postgres
rifasania Feb 26, 2025
25582f8
fixing:github-action:debug-yaml
rifasania Feb 26, 2025
a62d103
test
rifasania Feb 26, 2025
560713c
test2
rifasania Feb 26, 2025
3b94935
rollback
rifasania Feb 26, 2025
9cec989
test4
rifasania Feb 26, 2025
6eb0509
test5
rifasania Feb 26, 2025
723f114
md
rifasania Feb 26, 2025
66c0890
test7
rifasania Feb 26, 2025
3463452
test8
rifasania Feb 26, 2025
06c7d15
url
rifasania Feb 26, 2025
986cb20
tolong
rifasania Feb 26, 2025
2e209e1
fixed:github-action:add-migrations
rifasania Feb 26, 2025
fb20f56
fixed:testing
rifasania Feb 26, 2025
e7071ea
fixed:refactor-function-test
rifasania Feb 26, 2025
5f0783c
add-testing
rifasania Feb 27, 2025
e0f11f6
finished-ddl-operation
rifasania Feb 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
NODE_ENV=test
JWT_SECRET=supersecretkey
JWT_EXPIRES_IN=1h
DB_USERNAME=postgres
DB_PASSWORD=12345678
DB_NAME=database_services_test
DB_HOST=localhost
51 changes: 51 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Run Unit Tests

on:
push:
branches: [ main ]
pull_request:

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:13
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 12345678
POSTGRES_DB: database_services_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
NODE_ENV: test
DB_USERNAME: postgres
DB_PASSWORD: 12345678
DB_NAME: database_services_test
DB_HOST: localhost
JWT_SECRET: supersecretkey
JWT_EXPIRES_IN: 1h

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '20'

- name: Install dependencies
run: yarn install

- name: Run migrations
run: yarn migrate

- name: Run tests
run: yarn test
5 changes: 3 additions & 2 deletions .sequelizerc
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const path = require('path');
const path = require("path");
require('dotenv').config();
require("ts-node/register");

module.exports = {
"config": path.resolve(__dirname, "src/config/sequelize.config.js"),
config: path.resolve(__dirname, "src/config/sequelize.config.js"),
"models-path": path.resolve(__dirname, "src/models"),
"seeders-path": path.resolve(__dirname, "src/seeders"),
"migrations-path": path.resolve(__dirname, "src/migrations"),
Expand Down
9 changes: 8 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import js from "@eslint/js";
import tseslint from "@typescript-eslint/eslint-plugin";
import tsparser from "@typescript-eslint/parser";
import prettier from "eslint-plugin-prettier";

export default [
js.configs.recommended,
{
files: ["src/**/*.ts"],
ignores: ["dist/**", "node_modules/**"],
languageOptions: {
parser: tsparser,
},
plugins: {
"@typescript-eslint": tseslint,
prettier,
},
rules: {
"prettier/prettier": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-undef": "off",
},
},
{
files: ["src/migrations/*.js", "src/config/sequelize.config.js"],
files: ["src/config/sequelize.config.js"],
languageOptions: {
sourceType: "commonjs",
},
Expand Down
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require('dotenv').config({ path: '.env.test' });

/** @type {import('ts-jest').JestConfigWithTsJest} **/
module.exports = {
testEnvironment: "node",
transform: {
"^.+\.tsx?$": ["ts-jest",{}],
},
};
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"migrate": "sequelize-cli db:migrate --config=src/config/sequelize.config.js",
"migrate": "sequelize-cli db:migrate --config src/config/sequelize.config.js",
"seed": "sequelize-cli db:seed:all",
"prepare": "husky install",
"lint": "eslint src",
"test": "echo \"No test specified\" && exit 0"
"test": "jest"
},
"dependencies": {
"@tsconfig/node16": "^16.1.3",
Expand All @@ -20,7 +20,7 @@
"express": "^4.21.2",
"jsonwebtoken": "8.5.1",
"nanoid": "3",
"pg": "^8.13.1",
"pg": "^8.13.3",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.5",
"uuid": "^11.0.5"
Expand All @@ -29,18 +29,21 @@
"@eslint/js": "^9.20.0",
"@types/bcryptjs": "^2.4.6",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14",
"@types/jsonwebtoken": "^9.0.8",
"@types/node": "^22.13.1",
"@types/node": "^22.13.2",
"@types/sequelize": "^4.28.20",
"eslint": "^9.20.1",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.3",
"globals": "^15.14.0",
"husky": "^9.1.7",
"jest": "^29.7.0",
"lint-staged": "^15.4.3",
"nodemon": "^3.1.9",
"prettier": "^3.5.0",
"sequelize-cli": "^6.6.2",
"ts-jest": "^29.2.6",
"ts-node": "^10.9.2",
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.0"
Expand Down
23 changes: 23 additions & 0 deletions src/controllers/ddl-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Request, Response } from 'express';
import { sequelize } from '../config/database';
import { successResponse, errorResponse } from '../utils/response';
import { Operations } from '../types/ddl';
import { DDLExecutor } from '../operations/migrate';

export const migrate = async (req: Request, res: Response) => {
const { operations }: { operations: Operations[] } = req.body;
if (!operations || !Array.isArray(operations))
return errorResponse(res, 'Invalid payload structure', 400);

const transaction = await sequelize.transaction();

try {
await DDLExecutor.execute(operations, transaction);

await transaction.commit();
return successResponse(res, {}, 'DDL operations completed successfully');
} catch (error: any) {
await transaction.rollback();
return errorResponse(res, error.message, 500);
}
};
8 changes: 4 additions & 4 deletions src/controllers/user-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ export const registerUser = async (req: Request, res: Response) => {
return errorResponse(res, 'Name, email, and password are required', 400);
}

const existingUser = await UserRepository.findByEmail(email);
const existingUser = await UserRepository.findOne({ email });
if (existingUser) {
return errorResponse(res, 'Email is already registered', 400);
}

const hashedPassword = await hashPassword(password);
const apiKey = await generateApiKey();

const newUser = await UserRepository.createUser({
const newUser = await UserRepository.insert({
name,
email,
password: hashedPassword,
Expand All @@ -44,7 +44,7 @@ export const loginUser = async (req: Request, res: Response) => {
try {
const { email, password } = req.body;

const user = await UserRepository.findByEmail(email);
const user = await UserRepository.findOne({ email });
if (!user) {
return errorResponse(res, 'Unauthorized', 401);
}
Expand Down Expand Up @@ -79,7 +79,7 @@ export const getUserDetails = async (req: Request, res: Response) => {
}

const decoded: any = jwt.verify(token, ENV.JWT_SECRET);
const user = await UserRepository.findById(decoded.userId);
const user = await UserRepository.findOne({ id: decoded.userId });

if (!user) {
return errorResponse(res, 'Unauthorized', 401);
Expand Down
12 changes: 12 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import express, { Request, Response } from 'express';
import dotenv from 'dotenv';
import userRoutes from './routes/user-routes';
import authRoutes from './routes/auth-routes';
import ddlRoutes from './routes/ddl-routes';
import { sequelize } from './config/database';

sequelize
.sync({ force: true })
.then(async () => {
console.log('Database synchronized successfully.');
})
.catch((err) => {
console.error('Failed to synchronize database:', err);
});

dotenv.config();

Expand All @@ -14,6 +25,7 @@ const apiRouter = express.Router();

apiRouter.use('/auth', authRoutes);
apiRouter.use('/users', userRoutes);
apiRouter.use('/migrate', ddlRoutes);

app.use('/api', apiRouter);

Expand Down
1 change: 1 addition & 0 deletions src/middlewares/auth-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const authMiddleware = (
(req as any).user = decoded;
next();
} catch (error) {
console.error('Error:', error);
return errorResponse(res, 'Unauthorized', 401);
}
};
49 changes: 0 additions & 49 deletions src/migrations/20250206065327-create-user.js

This file was deleted.

51 changes: 51 additions & 0 deletions src/migrations/20250206065327-create-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { QueryInterface, DataTypes, Sequelize } from 'sequelize';

/** @type {import('sequelize-cli').Migration} */
export default {
async up(queryInterface: QueryInterface, sequelize: Sequelize) {
await queryInterface.sequelize.query(
'CREATE EXTENSION IF NOT EXISTS "uuid-ossp";',
);

await queryInterface.createTable('users', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: sequelize.literal('uuid_generate_v4()'),
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
apikey: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
created_at: {
allowNull: false,
type: DataTypes.DATE,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
},
updated_at: {
allowNull: false,
type: DataTypes.DATE,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
},
});
},

async down(queryInterface: QueryInterface) {
await queryInterface.dropTable('users');
},
};
34 changes: 34 additions & 0 deletions src/migrations/20250226042309-create-metadata-table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { QueryInterface, DataTypes, Sequelize } from 'sequelize';

/** @type {import('sequelize-cli').Migration} */
export default {
async up(queryInterface: QueryInterface, sequelize: Sequelize) {
await queryInterface.createTable('metadata_table', {
id: {
primaryKey: true,
type: DataTypes.UUID,
defaultValue: sequelize.literal('uuid_generate_v4()'),
allowNull: false,
},
table_name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
created_at: {
type: DataTypes.DATE,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
allowNull: false,
},
updated_at: {
type: DataTypes.DATE,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
allowNull: false,
},
});
},

async down(queryInterface: QueryInterface) {
await queryInterface.dropTable('metadata_table');
},
};
Loading