diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ef7ded4f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,51 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [unreleased] + +### 🚀 Features + +- Profile educations endpoints +- Profile assessments endpoints +- Profile leave endpoints + +### 🐛 Bug Fixes + +- แก้ชนิด type ที่ reques + +### ⚙️ Miscellaneous Tasks + +- Git-cliff changelog +- Update CHANGELOG.md + +## [1.0.62] - 2024-03-13 + +### 🚀 Features + +- Profile certificate endpoints +- Profile honor endpoints +- Profile insignia endpoints +- Profile training endpoints +- Check if profile exists + +### 🐛 Bug Fixes + +- Missing id +- History response +- Wrong database save + +### 🚜 Refactor + +- *(utils)* Merge 2 statement of same conditions +- *(middlewares)* Add type to function parameter auth role +- History in database after first edit only + +### ⚙️ Miscellaneous Tasks + +- *(tsoa-setting)* Update to recursive path +- Add response example + +## [1.0.61] - 2024-03-11 + + diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..a51a8779 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,89 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file.\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="version-") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing s +trim = true +# postprocessors +postprocessors = [ + # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL +] + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = false +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + # Replace issue numbers + #{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, + # Check spelling of the commit with https://github.com/crate-ci/typos + # If the spelling is incorrect, it will be automatically fixed. + #{ pattern = '.*', replace_command = 'typos --write-changes -' }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "🚀 Features" }, + { message = "^fix", group = "🐛 Bug Fixes" }, + { message = "^doc", group = "📚 Documentation" }, + { message = "^perf", group = "⚡ Performance" }, + { message = "^refactor", group = "🚜 Refactor" }, + { message = "^style", group = "🎨 Styling" }, + { message = "^test", group = "🧪 Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore|^ci", group = "⚙️ Miscellaneous Tasks" }, + { body = ".*security", group = "🛡️ Security" }, + { message = "^revert", group = "◀️ Revert" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = false +# regex for matching git tags +# tag_pattern = "v[0-9].*" +# regex for skipping tags +# skip_tags = "" +# regex for ignoring tags +# ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" +# limit the number of commits included in the changelog. +limit_commits = 42 diff --git a/src/controllers/ProfileLeaveController.ts b/src/controllers/ProfileLeaveController.ts new file mode 100644 index 00000000..85ab7fb9 --- /dev/null +++ b/src/controllers/ProfileLeaveController.ts @@ -0,0 +1,121 @@ +import { + Body, + Controller, + Delete, + Example, + Get, + Patch, + Path, + Post, + Request, + Route, + Security, + Tags, +} from "tsoa"; +import { AppDataSource } from "../database/data-source"; +import { CreateProfileLeave, ProfileLeave, UpdateProfileLeave } from "../entities/ProfileLeave"; +import HttpSuccess from "../interfaces/http-success"; +import HttpStatus from "../interfaces/http-status"; +import HttpError from "../interfaces/http-error"; +import { ProfileLeaveHistory } from "../entities/ProfileLeaveHistory"; +import { RequestWithUser } from "../middlewares/user"; +import { Profile } from "../entities/Profile"; + +@Route("api/v1/org/profile/leave") +@Tags("ProfileLeave") +@Security("bearerAuth") +export class ProfileLeaveController extends Controller { + private profileRepo = AppDataSource.getRepository(Profile); + private leaveRepo = AppDataSource.getRepository(ProfileLeave); + private leaveHistoryRepo = AppDataSource.getRepository(ProfileLeaveHistory); + + @Get("{profileId}") + @Example({ + status: 200, + message: "สำเร็จ", + result: [], + }) + public async getLeave(@Path() profileId: string) { + const record = await this.leaveRepo.findBy({ profileId }); + return new HttpSuccess(record); + } + + @Get("history/{leaveId}") + @Example({ + status: 200, + message: "สำเร็จ", + result: [], + }) + public async leaveHistory(@Path() leaveId: string) { + const record = await this.leaveHistoryRepo.findBy({ + profileLeaveId: leaveId, + }); + return new HttpSuccess(record); + } + + @Post() + public async newLeave(@Request() req: RequestWithUser, @Body() body: CreateProfileLeave) { + if (!body.profileId) { + throw new HttpError(HttpStatus.BAD_REQUEST, "กรุณากรอก profileId"); + } + + const profile = await this.profileRepo.findOneBy({ id: body.profileId }); + + if (!profile) { + throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว"); + } + + const data = new ProfileLeave(); + + const meta = { + createdUserId: req.user.sub, + createdFullName: req.user.name, + lastUpdateUserId: req.user.sub, + lastUpdateFullName: req.user.name, + }; + + Object.assign(data, { ...body, ...meta }); + + await this.leaveRepo.save(data); + + return new HttpSuccess(); + } + + @Patch("{leaveId}") + public async editLeave( + @Request() req: RequestWithUser, + @Body() body: UpdateProfileLeave, + @Path() leaveId: string, + ) { + const record = await this.leaveRepo.findOneBy({ id: leaveId }); + + if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + + const history = new ProfileLeaveHistory(); + + Object.assign(history, { ...record, id: undefined }); + Object.assign(record, body); + history.profileLeaveId = leaveId; + record.lastUpdateFullName = req.user.name; + history.lastUpdateFullName = req.user.name; + + await Promise.all([this.leaveRepo.save(record), this.leaveHistoryRepo.save(history)]); + + return new HttpSuccess(); + } + + @Delete("{leaveId}") + public async deleteTraning(@Path() leaveId: string) { + await this.leaveHistoryRepo.delete({ + profileLeaveId: leaveId, + }); + + const result = await this.leaveRepo.delete({ id: leaveId }); + + if (result.affected && result.affected <= 0) { + throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); + } + + return new HttpSuccess(); + } +}