Compare commits

...

142 Commits

Author SHA1 Message Date
6de23161e9 XD (#100)
XD

MD

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-18 20:30:37 +01:00
5ccd28f815 fXDD (#99)
fXDD

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-12 00:29:14 +01:00
4f0cf7906d grading (#98)
grading

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-11 21:48:41 +01:00
90e4c77da8 XD (#97)
XD

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-11 20:59:01 +01:00
9b3aff4dd0 5fb44c0b2c2818016e8c23d3 (#96)
Add approvals

Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into 5fb44c0b2c2818016e8c23d3

XD

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-11 20:45:59 +01:00
11de1c5dfe XD (#95)
XD

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-10 22:12:44 +01:00
8f2baabaf2 fix (#94)
fix

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-10 22:10:11 +01:00
e558ca4b61 XD (#93)
XD

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-10 21:37:45 +01:00
de1cb296d3 feat/report (#92)
XD

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-10 21:34:12 +01:00
b68a8480bd feat/report (#91)
fix

fix

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-10 21:12:32 +01:00
2e1903429b Accept/Reject document 2021-01-10 19:21:41 +01:00
d612e74e13 ReportFields (#89)
XD

XD

XD

fix

rest

merge

 XD

XD

Co-authored-by: Michal Bohdanowicz <m.w.bohdanowicz@gmail.com>
2021-01-10 18:31:28 +01:00
520e986a55 PostmanTestRequest update 2021-01-10 09:40:30 +01:00
0ef4eeb0ac InternshipType and Documents added to list. ChangeStateComment field for accept and reject 2021-01-10 09:39:25 +01:00
6d8d7a7c0a Internship management 2021-01-09 22:11:55 +01:00
2779667ce8 snap 2020-11-28 22:16:34 +01:00
bc8410c89d Course requests 2020-11-28 22:09:44 +01:00
39a4c07a87 CourseController + new SearchArg for EditionSearchQuery 2020-11-27 00:10:39 +01:00
b9471fe26d Merge pull request 'Update '.build/deploy.sh'' (#84) from kadet-patch-3 into master 2020-11-15 13:42:52 +01:00
1ed5e53e73 Update '.build/deploy.sh' 2020-11-15 13:37:22 +01:00
e0359d579d Merge pull request 'Fast fix' (#83) from fixStaticPages into master 2020-11-14 23:01:02 +01:00
ca2d1aede4 Fast fix 2020-11-14 23:00:17 +01:00
4623127862 change endpoint to proper path (#82)
change endpoint to proper path

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-12 17:12:44 +01:00
9939f36900 fix/fix (#81)
fix/fix

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-12 01:15:55 +01:00
6d1103a6b9 fix/scan (#80)
form

dum dum

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-12 00:26:46 +01:00
2176fbe661 Postman endpoints tests 2020-11-08 13:28:49 +01:00
3a64467661 Refactor 2020-11-08 13:24:25 +01:00
78707bf3c5 Edition Create/Edit 2020-11-08 13:08:38 +01:00
74e3957274 Edition Get and Delete 2020-11-08 10:53:14 +01:00
df11ff9005 fix/whatervberve (#78)
fix

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-08 00:15:23 +01:00
42e3cb01f4 fix/whatervberve (#78)
add subjects

fix normal student login

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-08 00:05:48 +01:00
05fde36f41 Merge pull request 'Update 'src/InternshipSystem.Repository/DatabaseFiller.cs'' (#77) from kadet-patch-2 into master 2020-11-07 15:31:57 +01:00
37d9d68c82 Update 'src/InternshipSystem.Repository/DatabaseFiller.cs' 2020-11-07 15:31:48 +01:00
3c446802be Update filled internship types to match moodle ones (#76)
Update labels to match

Fix typo and references to entities

Update filled internship types to match moodle ones
2020-11-07 15:28:30 +01:00
4fa04557fb fix ifx (#75)
fix ifx

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-06 22:18:29 +01:00
78a1a53e3b fix (#74)
fix

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-06 21:43:20 +01:00
33149c255b fix internship registration (#73)
fix internship registration

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-06 21:29:37 +01:00
3b60331091 fix everything (#72)
fix everything

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-11-06 20:54:48 +01:00
80dda549bf fix double registration (#71)
fix double registration

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-27 20:57:20 +01:00
2da7349578 fix (#70)
fix

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-26 12:13:23 +01:00
3d5fa43135 scans (#69)
scans

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-25 22:30:05 +01:00
515f236632 change enum serialization (#68)
whatever

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-25 20:03:27 +01:00
976498dc50 change enum serialization (#68)
change enum serialization

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-18 11:19:50 +02:00
df1834d325 make proper result type for update (#67)
make proper result type for update

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-18 10:24:35 +02:00
6e7bdb72af feat/scan (#66)
scan fix

Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into master

Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into master

merge

add document scan endpoint

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-18 09:12:13 +02:00
614054acba feat/random (#65)
fix documents and other stuff

whatever

Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into master

merge

add document scan endpoint

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-18 02:16:22 +02:00
2f6a1ab324 Merge pull request 'Fix migrations' (#64) from MigrationFix into master 2020-10-15 18:57:20 +02:00
ea191fd4d9 Fix migrations 2020-10-15 18:56:27 +02:00
0fadca7bb2 fix/migration (#63)
add migration

merge

add document scan endpoint

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-10 15:35:41 +02:00
102ec1feac add_hours (#62)
Add hours to registration

merge

add document scan endpoint

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-08 21:57:20 +02:00
7124b457dc feat/documents (#61)
add get scan endpoint

add document scan endpoint

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-08 19:27:45 +02:00
b98ab42c4d fix/endpoints (#60)
add

Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into fix/endpoints

fix

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-04 13:30:28 +02:00
28859a601c fix (#59)
fix

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-04 12:48:29 +02:00
7d8e51212a clean up api bit (#58)
clean up api  bit

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-04 12:12:11 +02:00
1b80f058dc Merge pull request 'InternshipTypeNextFixes' (#57) from InternshipTypeNextFixes into master 2020-10-03 22:33:21 +02:00
589b6a7a04 new migration 2020-10-03 22:32:10 +02:00
e5868e4f63 InternshipType changes + new migration 2020-10-03 22:31:53 +02:00
603358c4eb endpoints ... 2020-10-03 21:38:52 +02:00
5ae4b983bb fix/tytokurwaniepotrafisznicporzadniezrobic (#55)
Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into fix/tytokurwaniepotrafisznicporzadniezrobic

KEK

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-03 00:36:49 +02:00
b4b05b7ef3 fix/tytokurwaniepotrafisznicporzadniezrobic (#55)
Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into fix/tytokurwaniepotrafisznicporzadniezrobic

KEK

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-02 23:21:08 +02:00
3a2b9b64d5 KEK (#54)
KEK

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-02 21:27:58 +02:00
8514e593fa doroboty (#53)
XDDDEEEE

gtfo

Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into doroboty

finaly

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-10-02 19:56:53 +02:00
0b27330931 Merge pull request 'Revert' (#52) from MoreEndpoints into master 2020-09-27 14:40:48 +02:00
74e0665aed Revert 2020-09-27 14:39:40 +02:00
e24e7e7471 Merge pull request 'One context again' (#51) from MoreEndpoints into master 2020-09-27 14:18:32 +02:00
2576fda4c8 One context again 2020-09-27 14:17:47 +02:00
eee47200f2 Merge pull request 'get edition info by guid fix + add course info' (#50) from MoreEndpoints into master 2020-09-27 12:24:15 +02:00
a06811fc9f get edition info by guid fix + add course info 2020-09-27 12:23:50 +02:00
1899b934b0 Merge pull request 'SubmitRegistrationForm - fixes' (#49) from MoreEndpoints into master 2020-09-27 12:03:46 +02:00
058d6d30bf SubmitRegistrationForm - fixes 2020-09-27 12:03:13 +02:00
12a1081c8d Merge pull request 'Fixes + GetCurrentEditionInternship' (#48) from MoreEndpoints into master 2020-09-27 11:26:33 +02:00
0d62134963 Fixes + GetCurrentEditionInternship 2020-09-27 11:25:03 +02:00
4963c8cbc4 Merge pull request 'longer EXPIRATION time' (#47) from MoreEndpoints into master 2020-09-27 09:11:34 +02:00
9991e4df5f longer EXPIRATION time 2020-09-27 09:10:44 +02:00
50f65cfe09 Merge pull request 'Auto migration next vesrion' (#46) from MoreEndpoints into master 2020-09-26 18:39:03 +02:00
12e952b0ce Auto migration next vesrion 2020-09-26 18:32:55 +02:00
09289183ab Merge pull request 'Auto migration mechanism - first attempt' (#45) from MoreEndpoints into master 2020-09-26 16:47:13 +02:00
1443abc391 Auto migration mechanism - first attempt 2020-09-26 16:45:38 +02:00
94ef521625 Merge pull request 'CRUD internshipType' (#44) from MoreEndpoints into master 2020-09-24 20:52:09 +02:00
78ebdbe33d CRUD internshipType 2020-09-24 20:51:18 +02:00
6d6cf68fe2 Merge pull request 'New static pages endpoints' (#43) from MoreEndpoints into master 2020-09-24 18:15:37 +02:00
d0972673d8 New static pages endpoints 2020-09-24 18:14:24 +02:00
7115060ebd Merge pull request 'SearchQuery changes + new student endpoints' (#41) from AnotherDbSet into master 2020-09-24 17:23:12 +02:00
02be2f9f2e SearchQuery changes + new student endpoints 2020-09-24 17:23:12 +02:00
0c58248d2a Merge pull request 'New DbContext and crud for companies' (#40) from AnotherDbSet into master 2020-09-23 12:40:42 +02:00
da052c19aa Student update current data and get current data 2020-09-23 12:38:44 +02:00
fa0448bef6 New DbContext and crud for companies 2020-09-23 09:18:54 +02:00
54dfcaa7e7 feat/authorization (#39)
move client

merge

add Gut authentication and authorization

merge

dummy

Co-authored-by: MaxchilKH <m.w.bohdanowicz@gmail.com>
2020-09-13 01:07:40 +02:00
5884a444a9 eng static pages fill 2020-09-12 17:23:51 +02:00
c89229d1a9 Rewrite Nip and PhoneNumber 2020-09-12 17:18:35 +02:00
7b7998f408 fixes 2020-09-12 16:49:44 +02:00
a06991a318 Rewrite InternshipType 2020-09-12 16:03:05 +02:00
a508f9f4a8 static pages 2020-09-12 14:17:57 +02:00
cb2afcb897 Resolve InternshipType List in Edition problem 2020-09-05 20:51:42 +02:00
c401081dc8 add IntetnshipService 2020-09-05 20:51:42 +02:00
d81ebc98fb Merge pull request 'InternshipRegistration - EndPoint implementation' (#34) from InternshipRegistration into master 2020-09-04 20:44:15 +02:00
203a6a597d cancellationToken 2020-08-31 18:21:16 +02:00
5054330f29 cleanign 2020-08-31 18:13:30 +02:00
35bc61676f sync master
# Conflicts:
#	src/InternshipSystem.Api/Controllers/EditionController.cs
#	src/InternshipSystem.Core/Entity/Edition.cs
#	src/InternshipSystem.Core/Entity/Internship/InternshipRegistration.cs
#	src/InternshipSystem.Core/Entity/Report.cs
2020-08-31 17:22:02 +02:00
4549d31b6e whatever 2020-08-29 17:25:48 +02:00
b11211a756 Bad request error message 2020-08-28 19:01:10 +02:00
93bc55a307 InternshipRegistration EndPoint implementation 2020-08-26 19:03:32 +02:00
7258c456ff Move validator 2020-08-17 20:23:37 +02:00
f0dba347c5 document mapping and validation 2020-08-17 20:14:52 +02:00
fd37591201 save changes 2020-08-17 19:05:30 +02:00
b6669351c4 Add document the beginning of implementation 2020-08-17 18:59:57 +02:00
48ec0e2439 Rejection reason added to Document 2020-08-17 18:01:45 +02:00
95c08da571 Merge pull request 'feat/model_api' (#30) from feat/model_api into master 2020-08-10 19:09:18 +02:00
1dc100f817 Add api draft 2020-08-10 19:03:26 +02:00
357b7637ce Add specialized objects to model 2020-08-10 16:38:25 +02:00
5d2e111d77 Merge pull request 'resolve nip and phoneNumber problem' (#29) from migrationVOL3 into master 2020-08-04 20:01:19 +02:00
ca65e634c1 resolve nip and phoneNumber problem 2020-08-04 20:01:19 +02:00
f3319eb3f8 Merge pull request 'next -> VOL3' (#28) from migrationVOL3 into master
3 3 3 3 3
2020-08-04 19:27:56 +02:00
b9010f5134 next VOL3 2020-08-04 19:26:10 +02:00
2f96bd0e89 Merge pull request 'Jews are behind everything' (#27) from fix/my_mind into master 2020-08-04 17:47:05 +02:00
526e84ecf8 Jews are behind everything 2020-08-04 17:45:55 +02:00
d5f3963a26 migration v2 2020-08-04 16:43:13 +02:00
a07d5f11dd Rewrite 2020-07-29 19:38:39 +02:00
af2f225f6c Oj tak tak modeliczq +49 2020-07-29 18:28:25 +02:00
ba31cd89df move crap around 2020-07-26 00:50:46 +02:00
d09a1bd25a Api prefix from env 2020-07-18 16:50:22 +02:00
99c094390a Update build data and swagger path (#24)
Update '.build/deploy.sh'

Update 'src/InternshipSystem.Api/Startup.cs'
2020-07-18 16:09:48 +02:00
f9e47194a8 Commit message lol 2020-07-16 16:34:30 +02:00
3f766af29f DB developer image 2020-07-14 19:27:17 +02:00
3e70d40ec8 Separate Mentor and first full version of DatabaseFiller 2020-07-14 18:34:04 +02:00
9e9b12cdd6 Add relation from Edition to Internships 2020-07-14 17:03:02 +02:00
d23f98d8c8 Merge pull request 'Robienie takich commitów jest męczące ziomeczku' (#21) from fix/babole into master 2020-07-14 16:56:15 +02:00
d21cda23bf Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into fix/babole 2020-07-14 16:55:27 +02:00
499f71c915 Robienie takich commitów jest męczące ziomeczku 2020-07-14 16:52:58 +02:00
bdf4f2b2f5 First version of building data 2020-07-11 16:28:40 +02:00
c0fa6e2e35 babole 2020-07-11 14:02:47 +02:00
0e2529594f cool 2020-07-11 13:18:10 +02:00
63b2f8ceef Sample controller, change internship type to an entity, setup db properly 2020-07-11 12:48:33 +02:00
d2c4bca9d5 Merge pull request 'Docker is a stupid cunt, fix dockerfile to work with project references' (#16) from fix/docker into master 2020-07-09 22:46:50 +02:00
4f62919434 Docker is a stupid cunt 2020-07-09 22:45:05 +02:00
fd5d6b0f48 Resolve project reference problem 2020-07-08 18:39:19 +02:00
7e181f6887 refactoring
Entity class InternshipEntity name changed to Internship
namespace Internship changed to InternshipSystem
2020-07-08 18:25:49 +02:00
67cddc1fea InternShipType enum - ForeignInternship 2020-07-08 17:14:32 +02:00
43cb068902 Add Mentor to Intership 2020-07-06 19:19:25 +02:00
61889318a7 add ef repository, initial migration (#9)
setup ef core repository & posgre, init migration

Co-authored-by: Maxchil <m.w.bohdanowicz@gmail.com>
2020-06-12 17:03:50 +02:00
d234246248 improve model based on IPP form and add ids to entities (#8)
Merge branch 'master' of http://git.kadet.net/system-praktyk/system-praktyk-api into feat/improve_model

change model according to request,  add Ids

ide configuration changes, add postgre to compose

Co-authored-by: Maxchil <m.w.bohdanowicz@gmail.com>
2020-06-12 16:51:28 +02:00
09f943e6c3 ide configuration changes, add postgre to compose (#7)
ide configuration changes, add postgre to compose

Co-authored-by: Maxchil <m.w.bohdanowicz@gmail.com>
2020-06-12 16:19:35 +02:00
136 changed files with 9082 additions and 369 deletions

View File

@ -1,5 +1,4 @@
BUILD_PATH=$1
cd $BUILD_PATH/.docker || exit 1
docker-compose up -d --build --force-recreate
cd $BUILD_PATH/src || exit 1
docker build -f ./InternshipSystem.Api/Dockerfile -t internship.api .

View File

@ -0,0 +1,10 @@
# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP .NET Core service.
version: '3.4'
services:
internship.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://+:80
volumes:
- ~/.vsdbg:/remote_debugger:rw

View File

@ -1,9 +1,30 @@
version: "3.3"
services:
internship.api:
image: internship.api:latest
build: ../src/Internship.Api
ports:
- 8080:80
internship.api:
image: internship.api:latest
environment:
CONNECTIONSTRINGS__INTERNSHIPDATABASE: "Host=db.postgres;Port=5432;Database=postgres;Username=postgres;Password=szwoniu"
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: http://+:80
SECURITYOPTIONS__SECRET: iewaiwie3aig9wi3chieBai9eephai
SECURITYOPTIONS__EXPIRATION: 1440 # 24h in minutes
SECURITYOPTIONS__BASEURL: https://logowanie.pg.edu.pl
SECURITYOPTIONS__TOKENPATH: /oauth2.0/accessToken
SECURITYOPTIONS__PROFILEPATH: /oauth2.0/profile
SECURITYOPTIONS__CLIENTID: PraktykiClientId
SECURITYOPTIONS__REDIRECTURL: https://system-praktyk.stg.kadet.net/user/login/check/pg
FILLER__USE_DEFAULT_DATA: "true"
depends_on:
- db.postgres
ports:
- 8080:80
db.postgres:
image: postgres:latest
restart: always
environment:
POSTGRES_PASSWORD: szwoniu
PGDATA: /postgres
ports:
- 5432:5432

24
.dockerignore Normal file
View File

@ -0,0 +1,24 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
README.md

52
.vscode/launch.json vendored
View File

@ -4,33 +4,31 @@
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/Internship.Api/bin/Debug/netcoreapp3.1/Internship.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Internship.Api",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/Internship.Api/bin/Debug/netcoreapp3.1/Internship.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Internship.Api",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
]
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"files.exclude": {
"**/bin": true,
"**/obj": true
}
}

78
.vscode/tasks.json vendored
View File

@ -7,22 +7,14 @@
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/Internship.Api/Internship.Api.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/Internship.Api/Internship.Api.csproj",
"${workspaceFolder}/src/InternshipSystem.Api/InternshipSystem.Api.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": "$msCompile"
},
{
@ -31,12 +23,70 @@
"type": "process",
"args": [
"watch",
"--project",
"${workspaceFolder}/src/InternshipSystem.Api/InternshipSystem.Api.csproj",
"run",
"${workspaceFolder}/src/Internship.Api/Internship.Api.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build: api",
"type": "docker-build",
"dockerBuild": {
"dockerfile": "src/InternshipSystem.Api/Dockerfile",
"tag": "internship.api",
"context": "src"
},
"problemMatcher": [],
},
{
"label": "down: api",
"type": "shell",
"command": "docker-compose",
"args": [
"-f",
".docker/docker-compose.yaml",
"down",
"--volumes",
"--remove-orphans"
]
},
{
"label": "up: api",
"type": "shell",
"command": "docker-compose",
"args": [
"-f",
".docker/docker-compose.yaml",
"up",
],
"dependsOn": [
"build: api",
"down: api"
],
"problemMatcher": []
},
{
"label": "restore",
"type": "shell",
"command": "dotnet",
"args": [
"restore"
],
"problemMatcher": []
},
{
"label": "publish",
"command": "dotnet",
"args": [
"publish",
"--output",
"./obj/docker/publish",
"--runtime",
"linux-x64"
]
}
]
}

View File

@ -5,17 +5,17 @@ VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A43DB15B-657C-4E02-BC1B-2670DD34D9E0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Internship.Api", "src\Internship.Api\Internship.Api.csproj", "{2C72839D-2E0B-4794-9324-3E9E622251E3}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InternshipSystem.Api", "src\InternshipSystem.Api\InternshipSystem.Api.csproj", "{2C72839D-2E0B-4794-9324-3E9E622251E3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Internship.Repository", "src\Internship.Repository\Internship.Repository.csproj", "{A4697640-0746-4720-9B20-923763612F1A}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InternshipSystem.Repository", "src\InternshipSystem.Repository\InternshipSystem.Repository.csproj", "{A4697640-0746-4720-9B20-923763612F1A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Internship.Core", "src\Internship.Core\Internship.Core.csproj", "{2C395A11-E3A2-4454-95A1-82A3EAE791EC}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InternshipSystem.Core", "src\InternshipSystem.Core\InternshipSystem.Core.csproj", "{2C395A11-E3A2-4454-95A1-82A3EAE791EC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{69F62FEE-6768-4B47-9295-4EF5E8E30BEA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Internship.Api.Test", "test\Internship.Api.Test\Internship.Api.Test.csproj", "{90365B6B-B2C6-49D9-84F7-F806B06C1AFE}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InternshipSystem.Api.Test", "test\InternshipSystem.Api.Test\InternshipSystem.Api.Test.csproj", "{90365B6B-B2C6-49D9-84F7-F806B06C1AFE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Internship.Core.Tests", "test\Internship.Core.Tests\Internship.Core.Tests.csproj", "{748544B4-CBDA-4123-9BD9-499F6C90567F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InternshipSystem.Core.Tests", "test\InternshipSystem.Core.Tests\InternshipSystem.Core.Tests.csproj", "{748544B4-CBDA-4123-9BD9-499F6C90567F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -0,0 +1,867 @@
{
"info": {
"_postman_id": "f0858747-6271-4176-9193-ed1f9be0d7d3",
"name": "StudentOperations",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "loginDev",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:8080/dev/login",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"dev",
"login"
]
}
},
"response": []
},
{
"name": "GetInternshipManagementList",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjEwMjY2MzcxLCJleHAiOjE2MTAzNTI3NzEsImlhdCI6MTYxMDI2NjM3MX0.PDrMjCHJlpN2FApL6rC-UAoH1ZWAnUImwWfTDhvB5wI",
"type": "string"
}
]
},
"method": "GET",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/internship?OrderByField=InternshipState&SortOrder=Asc",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"internship"
],
"query": [
{
"key": "OrderByField",
"value": "InternshipState"
},
{
"key": "SortOrder",
"value": "Asc"
}
]
}
},
"response": []
},
{
"name": "GetInternshipManagement",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjEwMjY2MzcxLCJleHAiOjE2MTAzNTI3NzEsImlhdCI6MTYxMDI2NjM3MX0.PDrMjCHJlpN2FApL6rC-UAoH1ZWAnUImwWfTDhvB5wI",
"type": "string"
}
]
},
"method": "GET",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/internship/1",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"internship",
"1"
]
}
},
"response": []
},
{
"name": "GetInternshipManagementAccept",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjEwMjY2MzcxLCJleHAiOjE2MTAzNTI3NzEsImlhdCI6MTYxMDI2NjM3MX0.PDrMjCHJlpN2FApL6rC-UAoH1ZWAnUImwWfTDhvB5wI",
"type": "string"
}
]
},
"method": "PUT",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "\"\""
},
"url": {
"raw": "http://localhost:8080/management/internship/accept/1",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"internship",
"accept",
"1"
]
}
},
"response": []
},
{
"name": "GetInternshipManagementReject",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjEwMjY2MzcxLCJleHAiOjE2MTAzNTI3NzEsImlhdCI6MTYxMDI2NjM3MX0.PDrMjCHJlpN2FApL6rC-UAoH1ZWAnUImwWfTDhvB5wI",
"type": "string"
}
]
},
"method": "PUT",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "\"Co żeś odrąbał andzeju\""
},
"url": {
"raw": "http://localhost:8080/management/internship/reject/1",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"internship",
"reject",
"1"
]
}
},
"response": []
},
{
"name": "loginEdition",
"request": {
"method": "POST",
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTUyLCJleHAiOjE2MDEyODM5NTEsImlhdCI6MTYwMTE5NzU1Mn0.0wcNQSXV55MFvEnRLC09nGwCqI7M2kuWdwe0EOV_C6Y",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "\"138da8a3-855c-4b17-9bd2-5f357679efa9\""
},
"url": {
"raw": "http://localhost:8080/access/loginEdition",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"access",
"loginEdition"
]
}
},
"response": []
},
{
"name": "currentInternship",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "http://localhost:8080/internshipRegistration",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"internshipRegistration"
]
}
},
"response": []
},
{
"name": "GetCourseList",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "GET",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/course",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"course"
]
}
},
"response": []
},
{
"name": "GetCourse",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "GET",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/course/1",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"course",
"1"
]
}
},
"response": []
},
{
"name": "CreateCourse",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "PUT",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\r\n \"id\": null,\r\n \"name\": \"testowy\",\r\n \"nameEng\": \"test\"\r\n}"
},
"url": {
"raw": "http://localhost:8080/management/course",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"course"
]
}
},
"response": []
},
{
"name": "UpdateCourse",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "PUT",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\r\n \"id\": 3,\r\n \"name\": \"testowyUp\",\r\n \"nameEng\": \"testUp\"\r\n}"
},
"url": {
"raw": "http://localhost:8080/management/course",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"course"
]
}
},
"response": []
},
{
"name": "DeleteCourse",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "DELETE",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/course/3",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"course",
"3"
]
}
},
"response": []
},
{
"name": "GetEditionManagementListByCourse",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "GET",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/editions?course=1",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"editions"
],
"query": [
{
"key": "course",
"value": "1"
}
]
}
},
"response": []
},
{
"name": "GetEditionManagementList",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "GET",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/editions",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"editions"
]
}
},
"response": []
},
{
"name": "GetEditionManagement",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA0ODI3ODgzLCJleHAiOjE2MDQ5MTQyODMsImlhdCI6MTYwNDgyNzg4M30.a3mMm3Zk3xpfsmIwlqtpjgWgTNEXv8O4hH_V_L9UFZo",
"type": "string"
}
]
},
"method": "GET",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/editions/138da8a3-855c-4b17-9bd2-5f357679efa9",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"editions",
"138da8a3-855c-4b17-9bd2-5f357679efa9"
]
}
},
"response": []
},
{
"name": "CreateEdition",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "PUT",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\r\n \"editionStart\": \"2020-11-15T11:02:04.002Z\",\r\n \"editionFinish\": \"2020-11-25T11:02:04.002Z\",\r\n \"reportingStart\": \"2020-11-20T11:02:04.002Z\",\r\n \"course\": {\r\n \"id\": 1,\r\n \"name\": \"Informatyka\",\r\n \"nameEng\": \"Informatics\"\r\n },\r\n \"availableSubjectsIds\": [2],\r\n \"availableInternshipTypesIds\": [8]\r\n}"
},
"url": {
"raw": "http://localhost:8080/management/edition",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"edition"
]
}
},
"response": []
},
{
"name": "UpdateEdition",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA2NTk2MzI5LCJleHAiOjE2MDY2ODI3MjksImlhdCI6MTYwNjU5NjMyOX0.O-dLJNI3-tNj96xOpz8LE7zrysmJkN069A3Zku2A7S0",
"type": "string"
}
]
},
"method": "PUT",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\r\n \"id\": \"138da8a3-855c-4b17-9bd2-5f357679efa9\",\r\n \"editionStart\": \"2020-11-15T11:02:04.002Z\",\r\n \"editionFinish\": \"2020-11-20T11:02:04.002Z\",\r\n \"reportingStart\": \"2020-11-17T11:02:04.002Z\",\r\n \"course\": {\r\n \"id\": 2,\r\n \"name\": \"Inżynieria Biomedyczna\",\r\n \"nameEng\": \"Biomedical Engineering\"\r\n },\r\n \"availableSubjectsIds\": [3],\r\n \"availableInternshipTypesIds\": [3]\r\n}"
},
"url": {
"raw": "http://localhost:8080/management/editions",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"editions"
]
}
},
"response": []
},
{
"name": "DeleteEditionManagement",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjA0ODI3ODgzLCJleHAiOjE2MDQ5MTQyODMsImlhdCI6MTYwNDgyNzg4M30.a3mMm3Zk3xpfsmIwlqtpjgWgTNEXv8O4hH_V_L9UFZo",
"type": "string"
}
]
},
"method": "DELETE",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/management/editions/be1a4439-0360-477c-85c5-e28bc8542883",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"management",
"editions",
"be1a4439-0360-477c-85c5-e28bc8542883"
]
}
},
"response": []
},
{
"name": "updateRegistration",
"request": {
"method": "PUT",
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\r\n \"company\": {\r\n \"id\": 1,\r\n \"nip\": null,\r\n \"name\": null\r\n },\r\n \"branchOffice\": {\r\n \"id\": 1,\r\n \"street\": null,\r\n \"building\": null,\r\n \"city\": null,\r\n \"postalCode\": null,\r\n \"country\": null\r\n },\r\n \"start\": \"2020-07-27T09:43:49.094Z\",\r\n \"end\": \"2020-09-27T09:43:49.094Z\",\r\n \"type\": {\r\n \"id\": 2,\r\n \"type\": null,\r\n \"description\": null,\r\n \"descriptionEng\": null\r\n }\r\n}"
},
"url": {
"raw": "http://localhost:8080/internshipRegistration",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"internshipRegistration"
]
}
},
"response": []
},
{
"name": "GetRegisteredEditions",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"url": {
"raw": "http://localhost:8080/editions",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"editions"
]
}
},
"response": []
},
{
"name": "GetEditionsConfiguration",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"type": "text",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE"
},
{
"key": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"url": {
"raw": "http://localhost:8080/editions/138da8a3-855c-4b17-9bd2-5f357679efa9",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"editions",
"138da8a3-855c-4b17-9bd2-5f357679efa9"
]
}
},
"response": []
},
{
"name": "RegisterForEdition",
"request": {
"method": "POST",
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZpcnN0bmFtZSIsImZhbWlseV9uYW1lIjoibGFzdG5hbWUiLCJQZXJzb25OdW1iZXIiOiIxIiwibmJmIjoxNjAxMTk3NTYzLCJleHAiOjE2MDEyODM5NjMsImlhdCI6MTYwMTE5NzU2MywiRWRpdGlvbiI6IjEzOGRhOGEzLTg1NWMtNGIxNy05YmQyLTVmMzU3Njc5ZWZhOSJ9.l7QK1eUIJexnDaFKZ9yx3NWxmB2KrvPpjLUuuP1EJyE",
"type": "text"
},
{
"key": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "\"138da8a3-855c-4b17-9bd2-5f357679efa9\""
},
"url": {
"raw": "http://localhost:8080/register",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"register"
]
}
},
"response": []
}
]
}

49
README.MD Normal file
View File

@ -0,0 +1,49 @@
# Uruchomienie
Aby uruchomić środowisko deweloperskie wystarczy
```bash
docker build -f ./InternshipSystem.Api/Dockerfile -t internship.api .
docker-compose -f ./.docker/docker-compose.yaml --build --volumes
```
# Opis struktury
Opis struktury projektu
## src/
zawiera kod podzielony na 3 projekty
- Api - Definicja interfejsu wystawianego dla częsci frontendowej
- Core - Logika biznesowa aplikacji, definicja domeny.
- Repository - Definicja repozytorium EFCore
## test/
Zawiera testy jednostkowe projektu. Przy pisaniu wykorzystano Machine-Specification
## .build/
Folder zawierający pliki definiujące CI/CD projektu
## .docker/
Folder zawiera pliki docker-compose przeznaczone do uruchamiania środowiska deweloperskiego, stanowiące również dokumentacje zmiennych środowiskowych konfigurujących projekt
```yaml
CONNECTIONSTRINGS__INTERNSHIPDATABASE: "Host=db.postgres;Port=5432;Database=postgres;Username=postgres;Password=password"
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: http://+:80
SECURITYOPTIONS__SECRET: secret
SECURITYOPTIONS__EXPIRATION: 1440 # 24h in minutes
SECURITYOPTIONS__BASEURL: https://logowanie.pg.edu.pl
SECURITYOPTIONS__TOKENPATH: /oauth2.0/accessToken
SECURITYOPTIONS__PROFILEPATH: /oauth2.0/profile
SECURITYOPTIONS__CLIENTID: PraktykiClientId
SECURITYOPTIONS__REDIRECTURL: https://system-praktyk.net/user/login/check/pg
```
## props/
Folder ze współdzieloną konfiguracją dla grup projektów, np. wersji bibliotek używanych przy pisaniu testów.

View File

@ -1,5 +0,0 @@
{
"sdk": {
"version": "3.1.200"
}
}

View File

@ -0,0 +1,10 @@
<Project>
<ItemGroup>
<PackageReference Include="Machine.Fakes.FakeItEasy" Version="2.11.0" />
<PackageReference Include="Machine.Specifications" Version="1.0.0" />
<PackageReference Include="Machine.Specifications.Runner.VisualStudio" Version="2.10.1" />
<PackageReference Include="Machine.Specifications.Should" Version="1.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
</Project>

View File

@ -1,2 +0,0 @@
bin\
obj\

View File

@ -1,16 +0,0 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build-env
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "./Internship.Api.dll"]

View File

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<!-- <GenerateDocumentationFile>true</GenerateDocumentationFile>-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.3.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers" />
</ItemGroup>
</Project>

View File

@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
namespace Internship.Api
{
public class Startup
{
private IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) =>
Configuration = configuration;
public void ConfigureServices(IServiceCollection services) =>
services
.AddSwaggerGen(options =>
options.SwaggerDoc("v1", new OpenApiInfo {Title = "InternshipSystem Api - TEST", Version = "v1"}))
.AddControllers()
;
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app
.UseSwagger()
.UseSwaggerUI(options => options.SwaggerEndpoint("/swagger/v1/swagger.json", "InternshipSystem Api"))
.UseHttpsRedirection()
.UseRouting()
.UseAuthorization()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View File

@ -1,11 +0,0 @@
namespace Internship.Core
{
public struct Address
{
public string Street { get; set; }
public string Building { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
}

View File

@ -1,8 +0,0 @@
namespace Internship.Core
{
public class BranchOffice
{
public Company Company { get; set; }
public Address Address { get; set; }
}
}

View File

@ -1,12 +0,0 @@
using System;
namespace Internship.Core
{
public class Company
{
public string Name { get; set; }
public RangeOfActivities Range { get; set; }
public Uri SiteAddress { get; set; }
public string Nip { get; set; }
}
}

View File

@ -1,6 +0,0 @@
namespace Internship.Core
{
public class Course
{
}
}

View File

@ -1,18 +0,0 @@
using System;
namespace Internship.Core
{
public struct DeanAcceptance
{
public DateTime AcceptanceDate { get; set; }
public bool IsDeansAcceptanceRequired { get; set; }
public DeanAcceptanceReason Reason { get; set; }
}
public enum DeanAcceptanceReason
{
Semester,
Delay,
Whatever
}
}

View File

@ -1,12 +0,0 @@
using System;
namespace Internship.Core
{
public class Edition
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public DateTime IPPDeadlineDate { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using System;
namespace Internship.Core
{
public struct Insurance
{
public DateTime InsuranceDeliveryDate { get; set; }
public bool IsInsuranceRequired { get; set; }
}
}

View File

@ -1,14 +0,0 @@
using System;
namespace Internship.Core
{
public class Intern
{
public string AlbumNumber { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public Course Course { get; set; }
public int Semester { get; set; }
}
}

View File

@ -1,7 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -1,21 +0,0 @@
using System;
namespace Internship.Core
{
public class Internship
{
public Intern Intern { get; set; }
public Edition Edition { get; set; }
public Report Report { get; set; }
public BranchOffice BranchOffice { get; set; }
public InternshipProgram Program { get; set; }
public InternshipType Type { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool IsAccepted { get; set; }
public DeanAcceptance DeanAcceptance { get; set; }
public Insurance Insurance { get; set; }
public int InternshipLengthInWeeks { get; set; }
}
}

View File

@ -1,6 +0,0 @@
namespace Internship.Core
{
public class InternshipProgram
{
}
}

View File

@ -1,8 +0,0 @@
namespace Internship.Core
{
public enum InternshipType
{
B2B,
Free
}
}

View File

@ -1,6 +0,0 @@
namespace Internship.Core
{
public enum RangeOfActivities
{
}
}

View File

@ -1,6 +0,0 @@
namespace Internship.Core
{
public class Report
{
}
}

View File

@ -1,8 +0,0 @@
using System;
namespace Internship.Repository
{
public class Class1
{
}
}

View File

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,45 @@
using System;
using AutoMapper;
using InternshipSystem.Api.Queries;
using InternshipSystem.Api.Result;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity;
using InternshipSystem.Core.Entity.Internship;
using InternshipSystem.Core.UglyOrmArtifacts;
namespace InternshipSystem.Api
{
public class ApiProfile : Profile
{
public ApiProfile()
{
CreateMap<DocumentPublishRequest, Document>();
CreateMap<Edition, EditionResult>()
.ForMember(
result => result.Status,
opt => opt.MapFrom(edition => edition.IsOpen ? "Open" : "Archival"));
CreateMap<Edition, EditionManagementResult>();
CreateMap<Edition, EditionDetailsResult>();
CreateMap<Edition, EditionConfigurationResult>();
CreateMap<InternshipSubject, InternshipSubject>();
CreateMap<EditionSubject, InternshipSubject>()
.IncludeMembers(es => es.Subject);
CreateMap<EditionInternshipType, InternshipType>()
.IncludeMembers(eit => eit.InternshipType);
CreateMap<ReportFieldEdition, ReportField>()
.IncludeMembers(e => e.Field);
CreateMap<ReportField, ReportField>();
CreateMap<InternshipType, InternshipType>();
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using IdentityServer4.Extensions;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity.Internship;
namespace InternshipSystem.Api.Commands
{
public class UpdateRegistrationForm
{
public UpdateCompany? Company { get; set; }
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
public UpdateMentor? Mentor { get; set; }
public List<long> Subjects { get; set; }
public int? Hours { get; set; }
public long? Type { get; set; }
}
public struct UpdateMentor
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
}
public struct UpdateCompany
{
public long? Id { get; set; }
public string Nip { get; set; }
public string Name { get; set; }
public UpdateBranchOffice? BranchOffice { get; set; }
public bool IsCustomUpdate => !Nip.IsNullOrEmpty() || !Name.IsNullOrEmpty();
}
public struct UpdateBranchOffice
{
public long? Id { get; set; }
public string Street { get; set; }
public string Building { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public bool IsCustomUpdate => !string.IsNullOrEmpty(Street) ||
!string.IsNullOrEmpty(Building) ||
!string.IsNullOrEmpty(City) ||
!string.IsNullOrEmpty(PostalCode) ||
!string.IsNullOrEmpty(Country);
}
}

View File

@ -0,0 +1,117 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Options;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace InternshipSystem.Api.Controllers
{
[Route("access")]
[ApiController]
public class AccessController : ControllerBase
{
private readonly InternshipDbContext _context;
private readonly JwtTokenService _tokenService;
private readonly GutCasClient _loginClient;
private readonly SecurityOptions _securityOptions;
public AccessController(
IOptions<SecurityOptions> options,
InternshipDbContext context,
JwtTokenService tokenService,
GutCasClient loginClient)
{
_context = context;
_tokenService = tokenService;
_loginClient = loginClient;
_securityOptions = options.Value;
}
[HttpPost("login")]
public async Task<ActionResult> Authenticate([FromBody] string code, CancellationToken cancellationToken)
{
var token = await _loginClient.GetCasTokenAsync(code, cancellationToken);
var casData = await _loginClient.GetProfileAsync(token, cancellationToken);
if (!long.TryParse(casData.PersonNumber, out var id))
{
return BadRequest();
}
var student = await _context.Students.FirstOrDefaultAsync(s => s.Id == id, cancellationToken: cancellationToken);
if (student == null)
{
student = CreateStudentWithCasData(casData);
await _context.Students.AddAsync(student, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, student.FirstName),
new Claim(ClaimTypes.Surname, student.LastName),
new Claim(InternshipClaims.PersonNumber, student.Id.ToString())
});
return Ok(_tokenService.generateToken(identity));
}
[HttpGet("/dev/login")]
public async Task<ActionResult> Authenticate(CancellationToken cancellationToken)
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "firstname"),
new Claim(ClaimTypes.Surname, "lastname"),
new Claim(InternshipClaims.PersonNumber, "1")
});
return Ok(_tokenService.generateToken(identity));
}
[HttpPost("loginEdition")]
[Authorize]
public async Task<ActionResult> LoginIntoEdition([FromBody] Guid editionId, [FromServices] User user, CancellationToken token)
{
var edition = await _context.Editions.FindAsync(editionId);
var hasInternship = await _context.Entry(edition)
.Collection(e => e.Internships)
.Query()
.AnyAsync(i => i.Student.Id == user.PersonNumber, token);
if (!hasInternship)
{
return Unauthorized("Student isn't registered for this edition");
}
var newIdentity = User.Identities.First();
newIdentity.AddClaim(new Claim(InternshipClaims.Edition, editionId.ToString()));
return Ok(_tokenService.generateToken(newIdentity));
}
private Student CreateStudentWithCasData(CasUserData casData)
{
var id = long.Parse(casData.PersonNumber);
var firstName = casData.FirstName;
var lastName = casData.LastName;
var email = casData.Mail.First(s => s.EndsWith("@student.pg.edu.pl"));
var albumNumber = int.Parse(casData.AlbumNumber);
return Student.CreateStudent(id, firstName, lastName, email, albumNumber);
}
}
}

View File

@ -0,0 +1,53 @@
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Mvc;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("admin")]
public class AdminController : ControllerBase
{
public AdminController(DatabaseFiller fillerService)
{
FillerService = fillerService;
}
public DatabaseFiller FillerService { get; }
// [HttpPost("fill")]
// public async Task<IActionResult> FillAll()
// {
// await FillerService.FillAll();
// return Ok();
// }
//
// [HttpPost("fill/companies")]
// public async Task<IActionResult> FillCompaniesAsync()
// {
// await FillerService.FillCompanies();
// return Ok();
// }
//
// [HttpPost("fill/internshipTypes")]
// public async Task<IActionResult> FillInternshipTypesAsync()
// {
// await FillerService.FillInternshipTypes();
// return Ok();
// }
//
// [HttpPost("fill/editions")]
// public async Task<IActionResult> FillEditionsAsync()
// {
// await FillerService.FillEditions();
// return Ok();
// }
//
// [HttpPost("fill/staticPages")]
// public async Task<IActionResult> FillStaticPagesAsync()
// {
// await FillerService.FillStaticPages();
// return Ok();
// }
}
}

View File

@ -0,0 +1,256 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Queries;
using InternshipSystem.Api.Queries.SearchQuery;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using static System.String;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("companies")]
public class CompaniesController : ControllerBase
{
public CompaniesController(InternshipDbContext context)
{
Context = context;
}
private InternshipDbContext Context { get; }
/// <summary>
/// Get companies matching provided paginated query
/// </summary>
/// <param name="searchQuery">Paginated query description</param>
/// <response code="200">Successfully retrieved Companies</response>
/// <response code="400">Search query was malformed</response>
/// <returns>Part of companies collection</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Authorize]
public async Task<ActionResult<IReadOnlyCollection<Company>>> SearchByNameAsync([FromQuery] CompanySearchQuery searchQuery, [FromServices] User user, CancellationToken cancellationToken) =>
await Context.Companies
.Where(c => c.Name.ToLower().Contains(searchQuery.Name.ToLower()))
.Where(c => c.Provider == 0 || c.Provider == user.PersonNumber)
.OrderBy(o => o.Name)
.Skip(searchQuery.Page * searchQuery.PerPage)
.Take(searchQuery.PerPage)
.ToListAsync(cancellationToken);
/// <summary>
/// Get company branches matching provided paginated query
/// </summary>
/// <param name="searchQuery">Paginated query description</param>
/// <param name="companyId"></param>
/// <response code="200">Successfully retrieved matching offices</response>
/// <response code="400">Search query was malformed</response>
/// <returns>Part of companies collection</returns>
[HttpGet("{companyId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Authorize]
public async Task<ActionResult<IReadOnlyCollection<BranchOffice>>> SearchBranchesByAddress([FromQuery] BranchOfficeSearchQuery searchQuery, long companyId, [FromServices] User user, CancellationToken token)
{
var company = await Context.Companies.Where(c => c.Id == companyId).FirstAsync(token);
return await Context.Entry(company)
.Collection(c => c.Branches)
.Query()
.Where(office => office.Address.City.ToLower().Contains(searchQuery.City.ToLower()))
.Where(office => office.Provider == 0 || office.Provider == user.PersonNumber)
.Skip(searchQuery.Page * searchQuery.PerPage)
.Take(searchQuery.PerPage)
.ToListAsync(token);
}
/// <summary>
/// Updates or add new company (if not new than contains id)
/// </summary>
/// <param name="companyForm"></param>
/// <response code="200">Successfully updated company</response>
/// <response code="400">Company form was malformed</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Company not found</response>
/// <returns></returns>
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> UpsertCompany([FromBody] CompanyForm companyForm, CancellationToken cancellationToken)
{
var validator = new CompanyForm.Validator();
var validationResult = await validator.ValidateAsync(companyForm, cancellationToken);
if (!validationResult.IsValid)
{
return BadRequest(validationResult.ToString());
}
if (companyForm.Id.HasValue)
{
var companyToUpdate = await Context.Companies.FindAsync(companyForm.Id.Value);
if (companyToUpdate == null)
{
return NotFound();
}
companyToUpdate.Name = IsNullOrEmpty(companyForm.Name) ? companyToUpdate.Name : companyForm.Name;
companyToUpdate.Nip = IsNullOrEmpty(companyForm.Nip) ? companyToUpdate.Nip : companyForm.Nip;
}
else
{
var newCompany = Company.CreateCompany(companyForm.Nip, companyForm.Name);
await Context.Companies.AddAsync(newCompany, cancellationToken);
}
await Context.SaveChangesAsync(cancellationToken);
return Ok();
}
/// <summary>
/// Deletes existing company by id
/// </summary>
/// <param name="companyId"></param>
/// <response code="200">Successfully deleted company</response>
/// <response code="400">Company id is empty</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Company not found</response>
/// <returns></returns>
[HttpDelete("{companyId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> DeleteCompany(long companyId, CancellationToken cancellationToken)
{
var companyToDelete = await Context.Companies
.Include(c => c.Branches)
.FirstOrDefaultAsync(c => c.Id == companyId, cancellationToken);
if (companyToDelete == null)
{
return NotFound();
}
Context.Companies.Remove(companyToDelete);
await Context.SaveChangesAsync(cancellationToken);
return Ok();
}
/// <summary>
/// Updates or add new branchOffice (if not new than contains id)
/// </summary>
/// <param name="branchOfficeForm"></param>
/// <param name="companyId"></param>
/// <response code="200">Successfully updated company branch office</response>
/// <response code="400">Branch office was malformed</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Company or branch office not found</response>
/// <returns></returns>
[HttpPut("{companyId}/branchOffices")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> UpdateBranch([FromBody] BranchOfficeForm branchOfficeForm, long companyId, CancellationToken cancellationToken)
{
var validator = new BranchOfficeForm.Validator();
var validationResult = await validator.ValidateAsync(branchOfficeForm, cancellationToken);
if (!validationResult.IsValid)
{
return BadRequest(validationResult.ToString());
}
var company = await Context.Companies
.Include(c => c.Branches)
.FirstOrDefaultAsync(c => c.Id == companyId, cancellationToken);
if (company == null)
{
return NotFound();
}
if (branchOfficeForm.Id.HasValue)
{
var branchOffice = company.Branches.First(b => b.Id == branchOfficeForm.Id);
if (branchOffice == null)
{
return NotFound();
}
branchOffice.Address.Country = IsNullOrEmpty(branchOfficeForm.Country) ? branchOffice.Address.Country : branchOfficeForm.Country;
branchOffice.Address.City = IsNullOrEmpty(branchOfficeForm.City) ? branchOffice.Address.City : branchOfficeForm.City;
branchOffice.Address.PostalCode = IsNullOrEmpty(branchOfficeForm.PostalCode) ? branchOffice.Address.PostalCode : branchOfficeForm.PostalCode;
branchOffice.Address.Street = IsNullOrEmpty(branchOfficeForm.Street) ? branchOffice.Address.Street : branchOfficeForm.Street;
branchOffice.Address.Building = IsNullOrEmpty(branchOfficeForm.Building) ? branchOffice.Address.Building : branchOfficeForm.Building;
}
else
{
var newBranchOffice = new BranchOffice
{
Address = new BranchAddress
{
Country = branchOfficeForm.Country,
City = branchOfficeForm.City,
PostalCode = branchOfficeForm.PostalCode,
Street = branchOfficeForm.Street,
Building = branchOfficeForm.Building,
}
};
company.Branches.Add(newBranchOffice);
}
await Context.SaveChangesAsync(cancellationToken);
return Ok();
}
/// <summary>
/// Deletes existing branchOffice
/// </summary>
/// <param name="branchOfficeId"></param>
/// <response code="200">Successfully deleted company branch office</response>
/// <response code="400">Branch office id is empty</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Company or branch office not found</response>
[HttpDelete("{companyId}/branchOffice/{branchOfficeId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> DeleteBranch(long companyId, long branchOfficeId, CancellationToken cancellationToken)
{
var company =
await Context.Companies
.Include(c => c.Branches)
.Where(c => c.Id == companyId)
.FirstOrDefaultAsync(cancellationToken);
if (company == null)
{
return NotFound();
}
var branchOffice = company.Branches.RemoveAll(b => b.Id == branchOfficeId);
await Context.SaveChangesAsync(cancellationToken);
return Ok();
}
}
}

View File

@ -0,0 +1,127 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Queries;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("management/course")]
public class CourseController : ControllerBase
{
private InternshipDbContext Context { get; }
public CourseController(InternshipDbContext context)
{
Context = context;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult<IReadOnlyCollection<Course>>> GetCourses(CancellationToken cancellationToken) =>
await Context.Courses
.ToListAsync(cancellationToken);
[HttpGet("{courseId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult<Course>> GetCourse(long courseId, CancellationToken cancellationToken)
{
var course = await Context.Courses
.Where(c => c.Id == courseId)
.FirstOrDefaultAsync(cancellationToken);
if (course == null)
{
return NotFound();
}
return Ok(course);
}
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> UpsertCourse([FromBody] CourseForm courseForm, CancellationToken cancellationToken)
{
var validator = new CourseForm.Validator();
var validationResult = await validator.ValidateAsync(courseForm, cancellationToken);
if (!validationResult.IsValid)
{
return BadRequest(validationResult.ToString());
}
if (courseForm.Id.HasValue)
{
var course = await Context.Courses.FindAsync(courseForm.Id.Value);
if (course == null)
{
return NotFound();
}
course.UpdateCourse(courseForm.Name, courseForm.NameEng);
}
else
{
var newCourse = Course.CreateCourse(courseForm.Name, courseForm.NameEng);
await Context.Courses.AddAsync(newCourse, cancellationToken);
}
await Context.SaveChangesAsync(cancellationToken);
return Ok();
}
[HttpDelete("{courseId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> DeleteCourse(long courseId, CancellationToken cancellationToken)
{
var courseToDelete = await Context.Courses
.Where(c => c.Id == courseId)
.FirstOrDefaultAsync(cancellationToken);
if (courseToDelete == null)
{
return NotFound();
}
var referencedEditions =
await Context.Editions
.Include(e => e.Course)
.Where(e => e.Course.Id == courseToDelete.Id)
.CountAsync(cancellationToken);
if (referencedEditions > 0)
{
return Conflict();
}
Context.Courses.Remove(courseToDelete);
await Context.SaveChangesAsync(cancellationToken);
return Ok();
}
}
}

View File

@ -0,0 +1,110 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("management/document")]
public class DocumentManagementController : ControllerBase
{
private InternshipDbContext Context { get; }
public DocumentManagementController(InternshipDbContext context)
{
Context = context;
}
[HttpDelete("{documentId}/delete")]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> DeleteDocument(long documentId, CancellationToken ct)
{
var internship = await Context.Internships
.Include(i => i.Documentation)
.FirstOrDefaultAsync(i => i.Documentation.Any(d => d.Id.Equals(documentId)), ct);
if (internship == null)
{
return NotFound();
}
internship.RemoveDocument(documentId);
await Context.SaveChangesAsync(ct);
return Ok();
}
[HttpPut("{documentId}/accept")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> AcceptDocument(long documentId, [FromBody] string comment, CancellationToken token)
{
var internship = await Context.Internships
.Include(i => i.Documentation)
.FirstOrDefaultAsync(i => i.Documentation.Any(d => d.Id.Equals(documentId)), token);
if (internship == null)
{
return NotFound();
}
var document = internship.Documentation
.FirstOrDefault(d => d.Id.Equals(documentId));
if (document == null)
{
return NotFound();
}
document.State = DocumentState.Accepted;
document.ChangeStateComment = string.IsNullOrEmpty(comment) ? null : comment;
await Context.SaveChangesAsync(token);
return Ok();
}
[HttpPut("{documentId}/reject")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> RejectDocument(long documentId, [FromBody] string comment, CancellationToken token)
{
var internship = await Context.Internships
.Include(i => i.Documentation)
.FirstOrDefaultAsync(i => i.Documentation.Any(d => d.Id.Equals(documentId)), token);
if (internship == null)
{
return NotFound();
}
var document = internship.Documentation
.FirstOrDefault(d => d.Id.Equals(documentId));
if (document == null)
{
return NotFound();
}
document.State = DocumentState.Rejected;
document.ChangeStateComment = string.IsNullOrEmpty(comment) ? null : comment;
await Context.SaveChangesAsync(token);
return Ok();
}
}
}

View File

@ -0,0 +1,181 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Queries;
using InternshipSystem.Api.Security;
using InternshipSystem.Api.Service;
using InternshipSystem.Core;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("document")]
public class DocumentsController : ControllerBase
{
private readonly InternshipDbContext _context;
private readonly FileValidator _fileValidator;
public DocumentsController(InternshipDbContext context, FileValidator fileValidator)
{
_context = context;
_fileValidator = fileValidator;
}
/// <summary>
/// Fill out required document,
/// </summary>
/// <param name="documentRequest">Documents Scan and description, and Id of filled document</param>
/// <response code="200">If change was successfully registered</response>
/// <response code="400">If the provided query was malformed</response>
/// <response code="404">Id doesn't match any required document</response>
/// <response code="401">This action is only available for authorized student registered for current edition</response>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult> AddDocumentToInternship(
[FromBody] DocumentPublishRequest documentRequest,
[FromServices] User user,
CancellationToken cancellationToken)
{
var validator = new DocumentPublishRequest.Validator();
var result = await validator.ValidateAsync(documentRequest, cancellationToken);
if (!result.IsValid)
{
return BadRequest(result.ToString());
}
var edition = await _context.Editions.FirstAsync(e => e.Id == user.EditionId, cancellationToken);
var internship =
await _context.Entry(edition)
.Collection(e => e.Internships)
.Query()
.Include(i => i.Documentation)
.FirstAsync(i => i.Student.Id == user.PersonNumber, cancellationToken);
try
{
internship.AddNewDocument(documentRequest.Description, documentRequest.Type);
await _context.SaveChangesAsync(cancellationToken);
}
catch (ArgumentException e)
{
return BadRequest(e.Message);
}
return Ok(internship.Documentation.First(d => d.Type == documentRequest.Type));
}
[HttpPut("{documentId}/scan")]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult> AddDocumentScan(long documentId, IFormFile documentScan, [FromServices] User user, CancellationToken cancellationToken)
{
await using var memoryStream = new MemoryStream();
await documentScan.CopyToAsync(memoryStream, cancellationToken);
if (!_fileValidator.IsValidFile(memoryStream.ToArray()))
{
return BadRequest("error.document.scan");
}
var edition = await _context.Editions.FirstAsync(e => e.Id == user.EditionId, cancellationToken);
var internship =
await _context.Entry(edition)
.Collection(e => e.Internships)
.Query()
.Include(i => i.Documentation)
.FirstAsync(i => i.Student.Id == user.PersonNumber, cancellationToken);
var document = await _context.Entry(internship)
.Collection(i => i.Documentation)
.Query()
.Include(d => d.Scan)
.FirstAsync(d => d.Id == documentId, cancellationToken);
document.Scan ??= new DocumentScan();
document.Scan.Size = memoryStream.Length;
document.Scan.Filename = documentScan.FileName;
document.Scan.Mime = _fileValidator.GetFileMime(memoryStream.ToArray());
document.Scan.File = memoryStream.ToArray();
document.State = DocumentState.Submitted;
await _context.SaveChangesAsync(cancellationToken);
return Ok();
}
[HttpGet("{documentId}/scan/download")]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult> GetDocumentScan(long documentId, [FromQuery] string disposition, [FromServices] User user, CancellationToken cancellationToken)
{
var edition = await _context.Editions.FirstAsync(e => e.Id == user.EditionId, cancellationToken);
var internship =
await _context.Entry(edition)
.Collection(e => e.Internships)
.Query()
.FirstAsync(i => i.Student.Id == user.PersonNumber, cancellationToken);
var document =
await _context.Entry(internship)
.Collection(i => i.Documentation)
.Query()
.Include(d => d.Scan)
.FirstOrDefaultAsync(d => d.Id == documentId, cancellationToken);
var stream = new MemoryStream(document.Scan.File);
Response.Headers.Add("Content-Disposition", new ContentDisposition
{
Inline = disposition == "inline",
FileName = document.Scan.Filename,
Size = document.Scan.Size
}.ToString());
return File(stream, document.Scan.Mime);
}
[HttpGet("{documentId}/scan")]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult> GetDocumentScanData(long documentId, [FromServices] User user, CancellationToken cancellationToken)
{
var edition = await _context.Editions.FirstAsync(e => e.Id == user.EditionId, cancellationToken);
var internship =
await _context.Entry(edition)
.Collection(e => e.Internships)
.Query()
.FirstAsync(i => i.Student.Id == user.PersonNumber, cancellationToken);
var scan =
await _context.Entry(internship)
.Collection(i => i.Documentation)
.Query()
.Include(d => d.Scan)
.Select(s => new {s.Id, s.Scan.Filename, s.Scan.Mime, s.Scan.Size})
.FirstOrDefaultAsync(d => d.Id == documentId, cancellationToken);
if (scan == null)
{
return NotFound();
}
return Ok(scan);
}
}
}

View File

@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using IdentityServer4.Extensions;
using InternshipSystem.Api.Result;
using InternshipSystem.Api.Security;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[Route("editions")]
public class EditionController : ControllerBase
{
private InternshipDbContext Context { get; }
private IMapper Mapper { get; }
public EditionController(InternshipDbContext context, IMapper mapper)
{
Context = context;
Mapper = mapper;
}
/// <summary>
/// Get accessible editions
/// </summary>
/// <response code="200">Editions accessible by the current user</response>
/// <response code="401">This action is only available for authorized student</response>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult<IEnumerable<EditionResult>>> GetAvailableEditions([FromServices] User user, CancellationToken token)
{
var editions =
await Context.Editions
.Where(edition =>
edition.Internships
.Any(internship => internship.Student.Id == user.PersonNumber))
.ProjectTo<EditionResult>(Mapper.ConfigurationProvider)
.ToListAsync(token);
if (editions.IsNullOrEmpty())
{
return NotFound();
}
return Ok(editions);
}
/// <summary>
/// Get accessible editions
/// </summary>
/// <response code="200">Editions accessible by the current user</response>
/// <response code="401">This action is only available for authorized student</response>
/// <response code="404"/>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<EditionResult>> GetEditionInfo(Guid id, CancellationToken token)
{
var edition =
await Context.Editions
.Where(e => e.Id == id)
.ProjectTo<EditionResult>(Mapper.ConfigurationProvider)
.FirstOrDefaultAsync(token);
if (edition == null)
{
return NotFound();
}
return Ok(edition);
}
/// <summary>
/// Get current edition's configuration
/// </summary>
/// <response code="200">Parameters of edition registered for by student</response>
/// <response code="401">This action is only available for authorized student registered for this edition edition</response>
/// <response code="404">Specified edition doesn't exist</response>
/// <returns></returns>
[HttpGet("current")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult<EditionConfigurationResult>> GetEditionsConfiguration([FromServices] User user, CancellationToken token)
{
var edition =
await Context.Editions
.Include(e => e.AvailableSubjects)
.Include(e => e.ReportSchema)
.ThenInclude(e => e.Field)
.Include(e => e.Course)
.Where(e => e.Id == user.EditionId)
.ProjectTo<EditionConfigurationResult>(Mapper.ConfigurationProvider)
.FirstOrDefaultAsync(token);
if (edition == null)
{
return NotFound();
}
return Ok(edition);
}
}
}

View File

@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using InternshipSystem.Api.Queries;
using InternshipSystem.Api.Queries.SearchQuery;
using InternshipSystem.Api.Result;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("management/editions")]
public class EditionManagementController : ControllerBase
{
private InternshipDbContext Context { get; }
private IMapper Mapper { get; }
public EditionManagementController(IMapper mapper, InternshipDbContext context)
{
Context = context;
Mapper = mapper;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult<IReadOnlyCollection<EditionManagementResult>>> GetEditions([FromQuery] EditionSearchQuery searchQuery, CancellationToken token) =>
await Context.Editions
.Include(e => e.Course)
.Where(p => !searchQuery.Course.HasValue || p.Course.Id == searchQuery.Course)
.ProjectTo<EditionManagementResult>(Mapper.ConfigurationProvider)
.Skip(searchQuery.Page * searchQuery.PerPage)
.Take(searchQuery.PerPage)
.ToListAsync(token);
[HttpGet("{editionId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult<EditionDetailsResult>> GetFullEdition(Guid editionId, CancellationToken token)
{
var edition = await Context.Editions
.Include(e => e.Course)
.Include(e => e.AvailableSubjects)
.ThenInclude(s => s.Subject)
.Include(e => e.AvailableInternshipTypes)
.ThenInclude(i => i.InternshipType)
.Include(e => e.ReportSchema)
.ThenInclude(er => er.Field)
.Where(e => e.Id == editionId)
.ProjectTo<EditionDetailsResult>(Mapper.ConfigurationProvider)
.FirstOrDefaultAsync(token);
if (edition == null)
{
return NotFound();
}
return Ok(edition);
}
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> UpsertEdition(EditionForm editionForm, CancellationToken token)
{
var validator = new EditionForm.Validator();
var validationResult = await validator.ValidateAsync(editionForm, token);
if (!validationResult.IsValid)
{
return BadRequest(validationResult.ToString());
}
if (editionForm.Id.HasValue)
{
var editionToUpdate = await Context.Editions
.Include(e => e.AvailableSubjects)
.Include(e => e.AvailableInternshipTypes)
.Include(e => e.ReportSchema)
.FirstOrDefaultAsync(e => e.Id == editionForm.Id.Value, token);
if (editionToUpdate == null)
{
return NotFound();
}
editionToUpdate.UpdateEdition(editionForm.EditionStart, editionForm.EditionFinish, editionForm.ReportingStart,
editionForm.Course, editionForm.AvailableSubjectsIds, editionForm.AvailableInternshipTypesIds, editionForm.ReportSchema);
if (!editionToUpdate.IsValidDates)
{
return BadRequest();
}
}
else
{
var newEdition =
Edition.CreateEdition(editionForm.EditionStart.Value, editionForm.EditionFinish.Value, editionForm.ReportingStart.Value,
editionForm.Course, editionForm.AvailableSubjectsIds, editionForm.AvailableInternshipTypesIds, editionForm.ReportSchema);
if (!newEdition.IsValidDates)
{
return BadRequest();
}
await Context.Editions.AddAsync(newEdition, token);
}
await Context.SaveChangesAsync(token);
return Ok();
}
[HttpDelete("{editionId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> DeleteEdition(Guid editionId, CancellationToken token)
{
var editionToDelete = await Context.Editions
.Include(e => e.AvailableSubjects)
.Include(e => e.AvailableInternshipTypes)
.Include(e => e.ReportSchema)
.FirstOrDefaultAsync(e => e.Id.Equals(editionId), token);
if (editionToDelete == null)
{
return NotFound();
}
var referencedInternships =
await Context
.Entry(editionToDelete)
.Collection(e => e.Internships)
.Query()
.CountAsync(token);
if (referencedInternships > 0)
{
return Conflict();
}
Context.Editions.Remove(editionToDelete);
await Context.SaveChangesAsync(token);
return Ok();
}
}
}

View File

@ -0,0 +1,56 @@
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Security;
using InternshipSystem.Core.Entity.Internship;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("internship")]
public class InternshipController : ControllerBase
{
private readonly InternshipDbContext _context;
public InternshipController(InternshipDbContext context)
{
_context = context;
}
/// <summary>
/// Get internship for current edition
/// </summary>
/// <response code="200">If current internship returned successfully</response>
/// <response code="401">This action is only available for authorized student registered for current edition</response>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult<Internship>> GetCurrentEditionInternship([FromServices] User user, CancellationToken cancellationToken)
{
var edition = await _context.Editions
.FindAsync(user.EditionId);
var internship = await _context.Entry(edition)
.Collection(e => e.Internships)
.Query()
.Include(i => i.Student)
.Include(i => i.InternshipRegistration)
.Include(i => i.InternshipRegistration.Company)
.Include(i => i.InternshipRegistration.BranchAddress)
.Include(i => i.InternshipRegistration.Type)
.Include(i => i.InternshipRegistration.Subjects)
.ThenInclude(subject => subject.Subject)
.Include(i => i.Report)
.Include(i => i.Documentation)
.SingleAsync(i => i.Student.Id == user.PersonNumber, cancellationToken);
return Ok(internship);
}
}
}

View File

@ -0,0 +1,190 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Queries.SearchQuery;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity.Internship;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("management/internship")]
public class InternshipManagementController : ControllerBase
{
private InternshipDbContext Context { get; }
public InternshipManagementController(InternshipDbContext context)
{
Context = context;
}
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult<IReadOnlyCollection<Internship>>> GetInternships([FromQuery] InternshipSearchQuery searchQuery, CancellationToken token)
{
var query =
Context.Internships
.Include(i => i.Edition)
.Include(i => i.InternshipRegistration)
.Include(i => i.InternshipRegistration.Type)
.Include(i => i.Student)
.Include(i => i.Documentation)
.Include(i => i.Report)
.Where(i => !searchQuery.EditionId.HasValue || i.Edition.Id == searchQuery.EditionId)
.Where(i => !searchQuery.InternshipState.HasValue || i.InternshipRegistration.State == searchQuery.InternshipState)
.Where(i => !searchQuery.InternshipState.HasValue || i.Report.State == searchQuery.ReportState)
.Where(i => !searchQuery.StudentAlbumNumber.HasValue || i.Student.AlbumNumber == searchQuery.StudentAlbumNumber)
.Where(i => string.IsNullOrEmpty(searchQuery.StudentFirstName) || i.Student.FirstName.ToLower().Contains(searchQuery.StudentFirstName.ToLower()))
.Where(i => string.IsNullOrEmpty(searchQuery.StudentLastName) || i.Student.LastName.ToLower().Contains(searchQuery.StudentLastName.ToLower()))
.Where(i => !searchQuery.DocumentWithState.HasValue || i.Documentation.Any(d => d.State == searchQuery.DocumentWithState))
.Skip(searchQuery.Page * searchQuery.PerPage)
.Take(searchQuery.PerPage);
if (searchQuery.OrderByField.ToLower().Equals("date"))
{
query = searchQuery.SortOrder.Equals(SortOrder.Desc) ?
query.OrderByDescending(i => i.InternshipRegistration.SubmissionDate) :
query.OrderBy(i => i.InternshipRegistration.SubmissionDate);
}
else if (searchQuery.OrderByField.ToLower().Equals("internshipstate"))
{
query = searchQuery.SortOrder.Equals(SortOrder.Desc) ?
query.OrderByDescending(i => i.InternshipRegistration.State) :
query.OrderBy(i => i.InternshipRegistration.State);
}
var result = await query.ToListAsync(token);
return Ok(result);
}
[HttpGet("{internshipId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult<Internship>> GetInternship(long internshipId, CancellationToken token)
{
var internship = await Context.Internships
.Include(i => i.Student)
.Include(i => i.InternshipRegistration)
.Include(i => i.InternshipRegistration.Company)
.Include(i => i.InternshipRegistration.BranchAddress)
.Include(i => i.InternshipRegistration.Type)
.Include(i => i.InternshipRegistration.Subjects)
.ThenInclude(subject => subject.Subject)
.Include(i => i.InternshipRegistration.Mentor)
.Include(i => i.Report)
.Include(i => i.Documentation)
.Include(i => i.Edition)
.Where(i => i.Id.Equals(internshipId))
.FirstOrDefaultAsync(token);
if (internship == null)
{
return NotFound();
}
return Ok(internship);
}
[HttpGet("{internshipId}/status")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> GetInternshipStatus(long internshipId, CancellationToken token)
{
var internship = await Context.Internships
.Include(i => i.InternshipRegistration)
.Include(i => i.Report)
.Include(i => i.Documentation)
.FirstOrDefaultAsync(i => i.Id.Equals(internshipId), token);
if (internship == null)
{
return NotFound();
}
return new JsonResult(new { Errors = internship.ValidateStatus() });
}
[HttpPut("{internshipId}/grade")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> GradeInternship(long internshipId, [FromBody] float grade, CancellationToken token)
{
var internship = await Context.Internships
.FirstOrDefaultAsync(i => i.Id.Equals(internshipId), token);
if (internship == null)
{
return NotFound();
}
internship.Grade = grade;
await Context.SaveChangesAsync(token);
return new JsonResult(new { Errors = internship.ValidateStatus() });
}
[HttpPut("{internshipId}/registration/accept")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> AcceptInternshipRegistration(long internshipId, [FromBody] string comment, CancellationToken token)
{
var internship = await Context.Internships
.Include(i => i.InternshipRegistration)
.FirstOrDefaultAsync(i => i.Id.Equals(internshipId), token);
if (internship == null)
{
return NotFound();
}
internship.InternshipRegistration.State = DocumentState.Accepted;
internship.InternshipRegistration.ChangeStateComment = string.IsNullOrEmpty(comment) ? null : comment;
await Context.SaveChangesAsync(token);
return Ok();
}
[HttpPut("{internshipId}/registration/reject")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> RejectInternshipRegistration(long internshipId, [FromBody] string comment, CancellationToken token)
{
var internship = await Context.Internships
.Include(i => i.InternshipRegistration)
.FirstOrDefaultAsync(i => i.Id.Equals(internshipId), token);
if (internship == null)
{
return NotFound();
}
internship.InternshipRegistration.State = DocumentState.Rejected;
internship.InternshipRegistration.ChangeStateComment = string.IsNullOrEmpty(comment) ? null : comment;
await Context.SaveChangesAsync(token);
return Ok();
}
}
}

View File

@ -0,0 +1,85 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Commands;
using InternshipSystem.Api.Security;
using InternshipSystem.Api.UseCases;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("internshipRegistration")]
public class InternshipRegistrationController : ControllerBase
{
private readonly InternshipDbContext _context;
public InternshipRegistrationController(InternshipDbContext dbContext)
{
_context = dbContext;
}
/// <summary>
/// Validate and add filled internship registration form
/// </summary>
/// <response code="200">If registration form was successfully added</response>
/// <response code="400">If the provided registration query was malformed</response>
/// <response code="401">This action is only available for authorized student registered for current edition</response>
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult> SubmitRegistrationForm(
[FromBody] UpdateRegistrationForm registrationCommand,
[FromServices] User user,
CancellationToken cancellationToken)
{
var edition = await _context.Editions
.Include(e => e.AvailableInternshipTypes)
.ThenInclude(t => t.InternshipType)
.Include(e => e.AvailableSubjects)
.ThenInclude(t => t.Subject)
.FirstAsync(e => e.Id == user.EditionId, cancellationToken);
var internship =
await _context
.Entry(edition)
.Collection(e => e.Internships)
.Query()
.Include(i => i.InternshipRegistration)
.ThenInclude(r => r.BranchAddress)
.Include(i => i.InternshipRegistration)
.ThenInclude(r => r.Company)
.Include(i => i.InternshipRegistration)
.ThenInclude(c => c.Company.Branches)
.Include(i => i.InternshipRegistration)
.ThenInclude(c => c.Type)
.Include(i => i.InternshipRegistration)
.ThenInclude(c => c.Subjects)
.Include(i => i.Documentation)
.Where(i => i.Student.Id == user.PersonNumber)
.FirstAsync(cancellationToken);
var useCase = new UpdateInternshipRegistrationUseCase(_context, internship, edition, user);
try
{
var (status, result) = await useCase.UpdateInternshipRegistration(registrationCommand, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return new JsonResult(new {Status = status, Errors = result});
}
catch (ArgumentException e)
{
return BadRequest(e.Message);
}
}
}
}

View File

@ -0,0 +1,171 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Queries;
using InternshipSystem.Api.Security;
using InternshipSystem.Core.Entity.Internship;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("internshipTypes")]
public class InternshipTypesController : ControllerBase
{
private InternshipDbContext Context { get; }
public InternshipTypesController(InternshipDbContext context)
{
Context = context;
}
/// <summary>
/// Get internship types available for current edition
/// </summary>
/// <returns>List of internship types for current edition</returns>
[HttpGet("current")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult<IEnumerable<InternshipType>>> GetInternshipTypesForEdition([FromServices] User user, CancellationToken cancellationToken)
{
var edition =
await Context.Editions
.Include(e => e.AvailableInternshipTypes)
.ThenInclude(e => e.InternshipType)
.Where(e => e.Id.Equals(user.EditionId))
.FirstOrDefaultAsync(cancellationToken: cancellationToken);
if (edition == null)
{
return NotFound();
}
return Ok(edition.AvailableInternshipTypes.Select(e => e.InternshipType));
}
/// <summary>
/// Get internship type by id
/// </summary>
/// <returns>Internship type</returns>
/// <response code="200">Internship type returned successfully</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Internship type not found</response>
[HttpGet("{internshipTypeId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult<InternshipType>> GetInternshipType(long internshipTypeId, CancellationToken cancellationToken)
{
return await Context.InternshipTypes.FindAsync(internshipTypeId);
}
/// <summary>
/// Get internship type list
/// </summary>
/// <returns>Internship type</returns>
/// <response code="200">Internship type list returned successfully</response>
/// <response code="401">This action is only available for authorized internship admin</response>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize]
public async Task<ActionResult<IEnumerable<InternshipType>>> SearchInternshipTypes([FromQuery] InternshipTypeSearchQuery searchQuery, CancellationToken cancellationToken)
{
return await Context.InternshipTypes
.Where(t => string.IsNullOrEmpty(searchQuery.Label) || t.Label.Contains(searchQuery.Label))
.Where(t => string.IsNullOrEmpty(searchQuery.LabelEng) || t.Label.Contains(searchQuery.LabelEng))
.OrderBy(t => t.Label)
.Skip(searchQuery.Page * searchQuery.PerPage)
.Take(searchQuery.PerPage)
.ToListAsync(cancellationToken);
}
/// <summary>
/// Add or update internship type, only available for coordinator
/// </summary>
/// <response code="200">Internship type updated successfully</response>
/// <response code="400">Invalid internship type form</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Internship type with selected id do not exist</response>
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> UpsertInternshipType([FromBody] InternshipTypeFrom internshipTypeFrom, CancellationToken cancellationToken)
{
var validator = new InternshipTypeFrom.Validator();
var validationResult = await validator.ValidateAsync(internshipTypeFrom, cancellationToken);
if (!validationResult.IsValid)
{
return BadRequest(validationResult.ToString());
}
if (internshipTypeFrom.Id.HasValue)
{
var internshipType = await Context.InternshipTypes.FindAsync(internshipTypeFrom.Id.Value);
if (internshipType == null)
{
return NotFound($"Internship type with id {internshipTypeFrom.Id} not found");
}
internshipType.Label = string.IsNullOrEmpty(internshipTypeFrom.Label) ? internshipType.Label : internshipTypeFrom.Label;
internshipType.LabelEng = string.IsNullOrEmpty(internshipTypeFrom.LabelEng) ? internshipType.LabelEng : internshipTypeFrom.LabelEng;
internshipType.Description = string.IsNullOrEmpty(internshipTypeFrom.Description) ? internshipType.Description : internshipTypeFrom.Description;
internshipType.DescriptionEng = string.IsNullOrEmpty(internshipTypeFrom.DescriptionEng) ? internshipType.DescriptionEng : internshipTypeFrom.DescriptionEng;
}
else
{
var newInternshipType = new InternshipType
{
Label = internshipTypeFrom.Label,
LabelEng = internshipTypeFrom.LabelEng,
Description = internshipTypeFrom.Description,
DescriptionEng = internshipTypeFrom.DescriptionEng,
};
await Context.InternshipTypes.AddAsync(newInternshipType, cancellationToken);
}
await Context.SaveChangesAsync(cancellationToken);
return Ok();
}
/// <summary>
/// Add or update internship type
/// </summary>
/// <response code="200">Internship type deleted successfully</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Internship type with selected id do not exist</response>
[HttpDelete("{internshipTypeId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> DeleteInternshipType(long internshipTypeId, CancellationToken cancellationToken)
{
var internshipTypeToDelete = await Context.InternshipTypes
.FirstOrDefaultAsync(t => t.Id.Equals(internshipTypeId), cancellationToken: cancellationToken);
if (internshipTypeToDelete == null)
{
return NotFound($"Internship type with id: {internshipTypeId} does not exist");
}
Context.InternshipTypes.Remove(internshipTypeToDelete);
await Context.SaveChangesAsync(cancellationToken);
return Ok($"Internship type with id: {internshipTypeId} deleted successfully");
}
}
}

View File

@ -0,0 +1,64 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Security;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("register")]
public class RegistrationController : ControllerBase
{
private readonly InternshipDbContext _context;
public RegistrationController(InternshipDbContext context)
{
_context = context;
}
/// <summary>
/// Register student for edition using provided registration code
/// </summary>
/// <param name="registrationCode">GUID of edition </param>
/// <returns></returns>
/// <response code="200">If the student was successfully registered</response>
/// <response code="400">If the provided registration code was malformed</response>
/// <response code="404">The registration code doesn't match any open edition</response>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<IActionResult> RegisterStudentForEdition([FromBody] Guid registrationCode, [FromServices] User user, CancellationToken token)
{
var edition = await _context.Editions
.Include(e => e.Internships)
.ThenInclude(i => i.Student)
.FirstOrDefaultAsync(e => e.Id.Equals(registrationCode), cancellationToken: token);
if (edition == null)
{
return NotFound();
}
var student = await _context.Students.FindAsync(user.PersonNumber);
try
{
edition.RegisterInternship(student);
await _context.SaveChangesAsync(token);
}
catch (ArgumentException e)
{
return BadRequest(e.Message);
}
return Ok();
}
}
}

View File

@ -0,0 +1,62 @@
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Security;
using InternshipSystem.Core.ValueObject;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace InternshipSystem.Api.Controllers
{
[Route("internship/report")]
public class ReportController : ControllerBase
{
private readonly InternshipDbContext _context;
public ReportController(InternshipDbContext context)
{
_context = context;
}
[HttpPost]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult> PostReport([FromBody] JObject reportValue, [FromServices] User user, CancellationToken ct)
{
var edition = await _context.Editions
.FindAsync(user.EditionId);
var internship = await _context.Entry(edition)
.Collection(e => e.Internships)
.Query()
.Include(i => i.Report)
.Include(i => i.Documentation)
.SingleAsync(i => i.Student.Id == user.PersonNumber, ct);
internship.Report.UpdateReport(reportValue.ToString(Formatting.None));
internship.AddNewDocument("", DocumentType.InternshipEvaluation);
await _context.SaveChangesAsync(ct);
return Ok();
}
[HttpGet]
[Authorize(Policy = Policies.RegisteredOnly)]
public async Task<ActionResult> GetReport([FromServices] User user, CancellationToken ct)
{
var edition = await _context.Editions
.FindAsync(user.EditionId);
var internship = await _context.Entry(edition)
.Collection(e => e.Internships)
.Query()
.Include(i => i.Report)
.SingleAsync(i => i.Student.Id == user.PersonNumber, ct);
return Ok(JsonConvert.DeserializeObject(internship.Report.Value));
}
}
}

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AutoMapper;
using InternshipSystem.Api.Queries.SearchQuery;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[Route("management/report")]
public class ReportFieldsController : ControllerBase
{
private readonly InternshipDbContext _context;
private readonly IMapper _mapper;
public ReportFieldsController(InternshipDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[HttpGet("fields")]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<IEnumerable<ReportField>> GetFields(FieldSearchQuery searchQuery, CancellationToken ct) =>
await _context.ReportFields
.Where(c => c.Label.ToLower().Contains(searchQuery.Label.ToLower()))
.OrderBy(o => o.Label)
.Skip(searchQuery.Page * searchQuery.PerPage)
.Take(searchQuery.PerPage)
.ToListAsync(ct);
[HttpPost("fields")]
[Authorize(Policy = Policies.IsOverseer)]
public async Task<ActionResult> CreateField([FromBody] FieldCreateRequest request, CancellationToken ct)
{
ReportField field;
switch (request.FieldType)
{
case FieldType.LongText:
case FieldType.ShortText:
field = new ReportField(request.Label, request.LabelEng, request.Description, request.DescriptionEng, request.FieldType);
break;
case FieldType.Select:
case FieldType.Radial:
case FieldType.Checkbox:
field = new ReportChoiceField(request.Label, request.LabelEng, request.Description, request.DescriptionEng, request.FieldType, request.Choices);
break;
default:
return BadRequest("Unknown field type");
}
try
{
await _context.ReportFields.AddAsync(field, ct);
await _context.SaveChangesAsync(ct);
}
catch (Exception e)
{
return BadRequest("Failed");
}
return Ok();
}
}
public class FieldSearchQuery : SearchQuery
{
public string Label { get; set; } = "";
}
public class FieldCreateRequest
{
public long? Id { get; set; }
public string Label { get; set; }
public string LabelEng { get; set; }
public string Description { get; set; }
public string DescriptionEng { get; set; }
public FieldType FieldType { get; set; }
public string[] Choices { get; set; }
}
}

View File

@ -0,0 +1,163 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Queries;
using InternshipSystem.Core;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[ApiController]
[Route("staticPage")]
public class StaticPagesController : ControllerBase
{
public StaticPagesController(InternshipDbContext context)
{
Context = context;
}
private InternshipDbContext Context { get; }
/// <summary>
/// Get all static pages, only available for coordinator
/// </summary>
/// <returns>List of static pages with titles and content</returns>
/// <response code="200">Static pages list returned successfully</response>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IList<StaticPage>>> GetStaticPages(CancellationToken cancellationToken) =>
await Context.StaticPages
.ToListAsync(cancellationToken);
/// <summary>
/// Get static page, only available for coordinator
/// </summary>
/// <param name="accessName">Name of page</param>
/// <returns>Static page title and content</returns>
/// <response code="200">Static page returned successfully</response>
/// <response code="404">Static page with given access name do not exist</response>
[HttpGet("{accessName}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<StaticPage>> GetStaticPage(string accessName, CancellationToken cancellationToken)
{
var page =
await Context.StaticPages
.Where(p => p.AccessName.Trim().ToLower().Equals(accessName.Trim().ToLower()))
.FirstOrDefaultAsync(cancellationToken);
if (page == null)
{
return NotFound();
}
return Ok(page);
}
/// <summary>
/// Add or update static page, only available for coordinator
/// </summary>
/// <response code="200">Static page updated successfully</response>
/// <response code="400">Static page form is not valid</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Static page with given id do not exist</response>
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> UpdateStaticPage([FromBody] StaticPageForm staticPageForm, CancellationToken cancellationToken)
{
var validator = new StaticPageForm.Validator();
var validationResult = await validator.ValidateAsync(staticPageForm, cancellationToken);
if (!validationResult.IsValid)
{
return BadRequest(validationResult.ToString());
}
if (staticPageForm.Id.HasValue)
{
var pageToUpdate = await Context.StaticPages.FindAsync(staticPageForm.Id);
if (pageToUpdate == null)
{
return NotFound($"Static page with id: {staticPageForm.Id} does not exist");
}
if (!string.IsNullOrEmpty(staticPageForm.AccessName))
{
var pageWithSameAccessName = await Context.StaticPages
.FirstOrDefaultAsync(sp => sp.AccessName.ToLower().Trim().Equals(staticPageForm.AccessName.ToLower().Trim()), cancellationToken: cancellationToken);
if (pageWithSameAccessName != null && !pageWithSameAccessName.Id.Equals(pageToUpdate.Id))
{
return BadRequest($"Static page with access name: {staticPageForm.AccessName} already exist");
}
}
pageToUpdate.AccessName = string.IsNullOrEmpty(staticPageForm.AccessName) ? pageToUpdate.AccessName : staticPageForm.AccessName;
pageToUpdate.Title = string.IsNullOrEmpty(staticPageForm.Title) ? pageToUpdate.Title : staticPageForm.Title;
pageToUpdate.TitleEng = string.IsNullOrEmpty(staticPageForm.TitleEng) ? pageToUpdate.TitleEng : staticPageForm.TitleEng;
pageToUpdate.Content = string.IsNullOrEmpty(staticPageForm.Content) ? pageToUpdate.Content : staticPageForm.Content;
pageToUpdate.ContentEng = string.IsNullOrEmpty(staticPageForm.ContentEng) ? pageToUpdate.ContentEng : staticPageForm.ContentEng;
}
else
{
var pageWithSameAccessName = await Context.StaticPages
.FirstOrDefaultAsync(sp => sp.AccessName.ToLower().Trim().Equals(staticPageForm.AccessName.ToLower().Trim()), cancellationToken: cancellationToken);
if (pageWithSameAccessName != null)
{
return BadRequest($"Static page with access name: {staticPageForm.AccessName} already exist");
}
var newStaticPage = new StaticPage
{
AccessName = staticPageForm.AccessName.ToLower().Trim(),
Title = staticPageForm.Title,
TitleEng = staticPageForm.TitleEng,
Content = staticPageForm.Content,
ContentEng = staticPageForm.ContentEng,
};
await Context.StaticPages.AddAsync(newStaticPage, cancellationToken);
}
await Context.SaveChangesAsync(cancellationToken);
return Ok($"Static page updated successfully");
}
/// <summary>
/// Delete static page, only available for coordinator
/// </summary>
/// <response code="200">Static page deleted successfully</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Static page with given access name do not exist</response>
[HttpDelete("{accessName}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> DeleteStaticPage(string accessName, CancellationToken cancellationToken)
{
var pageToDelete = await Context.StaticPages
.FirstOrDefaultAsync(sp => sp.AccessName.ToLower().Trim().Equals(accessName.ToLower().Trim()), cancellationToken: cancellationToken);
if (pageToDelete == null)
{
return NotFound($"Static page with access name: {accessName} does not exist");
}
Context.StaticPages.Remove(pageToDelete);
await Context.SaveChangesAsync(cancellationToken);
return Ok($"Static page with access name: {accessName} deleted successfully");
}
}
}

View File

@ -0,0 +1,143 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Queries;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.Controllers
{
[Route("students")]
[ApiController]
public class StudentsController : ControllerBase
{
private readonly InternshipDbContext _context;
public StudentsController(InternshipDbContext context)
{
_context = context;
}
/// <summary>
/// Get current student personal data
/// </summary>
/// <returns>Current student data</returns>
/// <response code="200">Current student data returned successfully</response>
/// <response code="401">his action is only available for authorized student</response>
[HttpGet("current")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize]
public async Task<ActionResult<Student>> GetCurrentStudentData([FromServices] User user, CancellationToken cancellationToken)
{
return await _context.Students.FindAsync(user.PersonNumber);
}
/// <summary>
/// Update current student personal data
/// </summary>
/// <response code="200">Current student data updated successfully</response>
/// <response code="401">his action is only available for authorized student</response>
[HttpPut("current")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize]
public async Task<ActionResult> UpdateCurrentStudentData([FromBody] CurrentStudentForm studentNewData, [FromServices] User user, CancellationToken cancellationToken)
{
var currentStudent = await _context.Students.FindAsync(user.PersonNumber);
currentStudent.AlbumNumber = studentNewData.AlbumNumber ?? currentStudent.AlbumNumber;
currentStudent.FirstName = string.IsNullOrEmpty(studentNewData.FirstName) ? currentStudent.FirstName : studentNewData.FirstName;
currentStudent.LastName = string.IsNullOrEmpty(studentNewData.LastName) ? currentStudent.LastName : studentNewData.LastName;
currentStudent.Email = string.IsNullOrEmpty(studentNewData.Email) ? currentStudent.Email : studentNewData.Email;
currentStudent.Course = string.IsNullOrEmpty(studentNewData.Course) ? currentStudent.Course : studentNewData.Course;
currentStudent.Semester = studentNewData.Semester ?? currentStudent.Semester;
await _context.SaveChangesAsync(cancellationToken);
return Ok();
}
/// <summary>
/// Get student personal data, only available for coordinator
/// </summary>
/// <returns>Student personal data</returns>
/// <response code="200">Student data returned successfully</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Student with given id do not exist</response>
[HttpGet("{studentPersonNumber}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult<Student>> GetStudentByPersonNumber(long studentPersonNumber, CancellationToken cancellationToken) =>
await _context.Students.FindAsync(studentPersonNumber);
/// <summary>
/// Search students personal data, only available for coordinator
/// </summary>
/// <returns>List of students personal data</returns>
/// <response code="200">List of student data</response>
/// <response code="401">This action is only available for authorized internship admin</response>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Authorize]
public async Task<ActionResult<IReadOnlyCollection<Student>>> SearchStudents([FromQuery] StudentSearchQuery searchQuery, CancellationToken cancellationToken) =>
await _context.Students
.Where(s => !searchQuery.AlbumNumber.HasValue || s.AlbumNumber.Equals(searchQuery.AlbumNumber))
.Where(s => string.IsNullOrEmpty(searchQuery.FirstName) || s.FirstName.ToLower().Contains(searchQuery.FirstName.ToLower()))
.Where(s => string.IsNullOrEmpty(searchQuery.LastName) || s.LastName.ToLower().Contains(searchQuery.LastName.ToLower()))
.OrderBy(s => s.AlbumNumber)
.Skip(searchQuery.Page * searchQuery.PerPage)
.Take(searchQuery.PerPage)
.ToListAsync(cancellationToken);
/// <summary>
/// Updates student personal data, only available for coordinator
/// </summary>
/// <response code="200">Student data updated successfully</response>
/// <response code="401">This action is only available for authorized internship admin</response>
/// <response code="404">Student with given id do not exist</response>
[HttpPut("{studentId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize]
public async Task<ActionResult> UpdateStudentData(long studentId, [FromBody] StudentForm studentNewData, CancellationToken cancellationToken)
{
var validator = new StudentForm.Validator();
var validationResult = await validator.ValidateAsync(studentNewData, cancellationToken);
if (!validationResult.IsValid)
{
return BadRequest(validationResult.ToString());
}
var currentStudent = await _context.Students.FindAsync(studentId);
if (currentStudent == null)
{
return NotFound();
}
currentStudent.AlbumNumber = studentNewData.AlbumNumber ?? currentStudent.AlbumNumber;
currentStudent.FirstName = string.IsNullOrEmpty(studentNewData.FirstName) ? currentStudent.FirstName : studentNewData.FirstName;
currentStudent.LastName = string.IsNullOrEmpty(studentNewData.LastName) ? currentStudent.LastName : studentNewData.LastName;
currentStudent.Email = string.IsNullOrEmpty(studentNewData.Email) ? currentStudent.Email : studentNewData.Email;
currentStudent.Course = string.IsNullOrEmpty(studentNewData.Course) ? currentStudent.Course : studentNewData.Course;
currentStudent.Semester = studentNewData.Semester ?? currentStudent.Semester;
await _context.SaveChangesAsync(cancellationToken);
return Ok();
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace InternshipSystem.Api.Converters
{
public class StringArrayConverter<TItem> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<TItem>>();
}
return new List<TItem>
{
token.ToObject<TItem>()
};
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<TItem>);
}
public override bool CanWrite => false;
}
}

View File

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS prep-env
COPY . ./
RUN mkdir /proj && cp --parents */*.csproj /proj
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build-env
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY --from=prep-env ./proj .
RUN dotnet restore ./InternshipSystem.Api
# Copy everything else and build
COPY . ./
RUN dotnet publish ./InternshipSystem.Api -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
RUN curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l /vsdbg
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "./InternshipSystem.Api.dll"]

View File

@ -0,0 +1,50 @@
using System;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.DependencyInjection;
namespace InternshipSystem.Api.Extensions
{
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseMigration(this IApplicationBuilder app)
{
bool done = false;
while (!done)
{
try
{
using var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope();
using var context = serviceScope.ServiceProvider.GetService<InternshipDbContext>();
context.Database.Migrate();
done = true;
}
catch (Exception)
{
// ignored
}
}
return app;
}
public static IApplicationBuilder UseDefaultData(this IApplicationBuilder app, bool useDefaultData)
{
using var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope();
using var context = serviceScope.ServiceProvider.GetService<InternshipDbContext>();
var filler = serviceScope.ServiceProvider.GetService<DatabaseFiller>();
context.Database.Migrate();
if (useDefaultData && !context.Editions.Any())
{
filler.FillAll().Wait();
}
return app;
}
}
}

View File

@ -0,0 +1,35 @@
using System.Text;
using InternshipSystem.Api.Options;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace InternshipSystem.Api.Extensions
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddStudentAuthentication(this IServiceCollection services)
{
var options = services.BuildServiceProvider().GetService<IOptions<SecurityOptions>>().Value;
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(options.Secret)),
ValidateAudience = false,
ValidateIssuer = false
};
});
return services;
}
}
}

View File

@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using InternshipSystem.Api.Options;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using JsonConverter = System.Text.Json.Serialization.JsonConverter;
namespace InternshipSystem.Api.Controllers
{
public class GutCasClient
{
private readonly HttpClient _client;
private readonly SecurityOptions _securityOptions;
public GutCasClient(HttpClient client, IOptions<SecurityOptions> options)
{
_securityOptions = options.Value;
client.BaseAddress = _securityOptions.BaseUrl;
_client = client;
}
public async Task<string> GetCasTokenAsync(string code, CancellationToken cancellationToken)
{
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "client_id", _securityOptions.ClientId },
{ "client_secret", _securityOptions.Secret },
{ "redirect_uri", _securityOptions.RedirectUrl.ToString() },
{ "code", code }
}),
RequestUri = _securityOptions.TokenPath
};
var response = await _client.SendAsync(request, cancellationToken);
var content = await response.Content.ReadAsStringAsync();
var value = JsonConvert.DeserializeObject<Dictionary<string, object>>(content);
return value["access_token"].ToString();
}
public async Task<CasUserData> GetProfileAsync(string token, CancellationToken cancellationToken)
{
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
Content = new StringContent(string.Empty),
RequestUri = _securityOptions.ProfilePath
};
request.Headers.Authorization = AuthenticationHeaderValue.Parse($"Bearer {token}");
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var response = await _client.SendAsync(request, cancellationToken);
var content = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<CasUserProfile>(content);
return result.Attributes;
}
}
}

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<NoWarn>$(NoWarn);1591;1573</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="10.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.0.1" />
<PackageReference Include="FluentValidation" Version="9.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.6.3" />
<PackageReference Include="IdentityServer4" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../InternshipSystem.Core/InternshipSystem.Core.csproj" />
<ProjectReference Include="../InternshipSystem.Repository/InternshipSystem.Repository.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,44 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using InternshipSystem.Api.Security;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace InternshipSystem.Api.ModelBinders
{
public class UserBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var principal = bindingContext.HttpContext.User;
if (principal == null)
{
return Task.CompletedTask;
}
Guid? editionGuid = null;
if (principal.FindFirst(InternshipClaims.Edition) != null
&& Guid.TryParse(principal.FindFirst(InternshipClaims.Edition).Value, out var edition))
{
editionGuid = edition;
}
var user = new User
{
Name = principal.FindFirst(ClaimTypes.Name).Value,
PersonNumber = long.Parse(principal.FindFirst(InternshipClaims.PersonNumber).Value),
EditionId = editionGuid
};
bindingContext.Result = ModelBindingResult.Success(user);
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using InternshipSystem.Api.Security;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
namespace InternshipSystem.Api.ModelBinders
{
public class UserBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(User))
{
return new BinderTypeModelBinder(typeof(UserBinder));
}
return null;
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Security.Policy;
namespace InternshipSystem.Api.Options
{
public class SecurityOptions
{
public string Secret { get; set; }
public double Expiration { get; set; }
public Uri BaseUrl { get; set; }
public Uri TokenPath { get; set; }
public Uri ProfilePath { get; set; }
public Uri RedirectUrl { get; set; }
public string ClientId { get; set; }
}
}

View File

@ -1,16 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;
using Serilog.Formatting.Compact;
namespace Internship.Api
namespace InternshipSystem.Api
{
public class Program
{

View File

@ -0,0 +1,36 @@
using FluentValidation;
namespace InternshipSystem.Api.Queries
{
public class BranchOfficeForm
{
public long? Id { get; set; }
public string Street { get; set; }
public string Building { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public class Validator : AbstractValidator<BranchOfficeForm>
{
public Validator()
{
RuleFor(b => b.Id).NotNull()
.When(c =>
string.IsNullOrEmpty(c.Country) || string.IsNullOrEmpty(c.City) ||
string.IsNullOrEmpty(c.PostalCode) || string.IsNullOrEmpty(c.Street) ||
string.IsNullOrEmpty(c.Building));
RuleFor(b => b.Country).NotEmpty()
.When(b => !b.Id.HasValue);
RuleFor(b => b.City).NotEmpty()
.When(b => !b.Id.HasValue);
RuleFor(b => b.PostalCode).NotEmpty()
.When(b => !b.Id.HasValue);
RuleFor(b => b.Street).NotEmpty()
.When(b => !b.Id.HasValue);
RuleFor(b => b.Building).NotEmpty()
.When(b => !b.Id.HasValue);
}
}
}
}

View File

@ -0,0 +1,24 @@
using FluentValidation;
namespace InternshipSystem.Api.Queries
{
public class CompanyForm
{
public long? Id { get; set; }
public string Nip { get; set; }
public string Name { get; set; }
public class Validator : AbstractValidator<CompanyForm>
{
public Validator()
{
RuleFor(c => c.Id).NotNull()
.When(c => string.IsNullOrEmpty(c.Nip) || string.IsNullOrEmpty(c.Name));
RuleFor(c => c.Nip).NotEmpty()
.When(c => !c.Id.HasValue);
RuleFor(c => c.Name).NotEmpty()
.When(c => !c.Id.HasValue);
}
}
}
}

View File

@ -0,0 +1,23 @@
using FluentValidation;
namespace InternshipSystem.Api.Queries
{
public class CourseForm
{
public long? Id { get; set; }
public string Name { get; set; }
public string NameEng { get; set; }
public class Validator : AbstractValidator<CourseForm>
{
public Validator()
{
RuleFor(c => c.Id).NotNull()
.When(c => string.IsNullOrWhiteSpace(c.Name));
RuleFor(c => c.Name).NotEmpty()
.When(c => !c.Id.HasValue);
}
}
}
}

View File

@ -0,0 +1,12 @@
namespace InternshipSystem.Api.Queries
{
public class CurrentStudentForm
{
public int? AlbumNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Course { get; set; }
public int? Semester { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using FluentValidation;
using InternshipSystem.Core.ValueObject;
using Microsoft.AspNetCore.Http;
namespace InternshipSystem.Api.Queries
{
public class DocumentPublishRequest
{
public string Description { get; set; }
public DocumentType Type { get; set; }
public class Validator : AbstractValidator<DocumentPublishRequest>
{
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity;
using InternshipSystem.Core.Entity.Internship;
namespace InternshipSystem.Api.Queries
{
public class EditionForm
{
public Guid? Id { get; set; }
public DateTime? EditionStart { get; set; }
public DateTime? EditionFinish { get; set; }
public DateTime? ReportingStart { get; set; }
public Course Course { get; set; }
public List<long> AvailableSubjectsIds { get; set; } = new List<long>();
public List<long> AvailableInternshipTypesIds { get; set; } = new List<long>();
public List<long> ReportSchema { get; set; } = new List<long>();
public class Validator : AbstractValidator<EditionForm>
{
public Validator()
{
RuleFor(e => e.Id).NotNull()
.When(e => !e.EditionStart.HasValue || !e.EditionFinish.HasValue
|| !e.ReportingStart.HasValue || e.Course == null);
RuleFor(e => e.EditionStart).NotEmpty()
.When(e => !e.Id.HasValue);
RuleFor(e => e.EditionFinish).NotEmpty()
.When(e => !e.Id.HasValue);
RuleFor(e => e.ReportingStart).NotEmpty()
.When(e => !e.Id.HasValue);
RuleFor(e => e.Course).NotNull()
.When(e => !e.Id.HasValue);
}
}
}
}

View File

@ -0,0 +1,27 @@
using FluentValidation;
namespace InternshipSystem.Api.Queries
{
public class InternshipTypeFrom
{
public long? Id { get; set; }
public string Label { get; set; }
public string LabelEng { get; set; }
public string Description { get; set; }
public string DescriptionEng { get; set; }
public class Validator : AbstractValidator<InternshipTypeFrom>
{
public Validator()
{
RuleFor(t => t.Id).NotNull()
.When(t =>
string.IsNullOrEmpty(t.Label) || string.IsNullOrEmpty(t.LabelEng));
RuleFor(t => t.Label).NotEmpty()
.When(t => !t.Id.HasValue);
RuleFor(t => t.LabelEng).NotEmpty()
.When(t => !t.Id.HasValue);
}
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using FluentValidation;
using InternshipSystem.Core.Entity.Internship;
namespace InternshipSystem.Api.Queries
{
public class RegistrationFormQuery
{
public CompanyForm Company { get; set; }
public BranchOfficeForm BranchOffice { get; set; }
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
public InternshipType Type { get; set; }
public class Validator : AbstractValidator<RegistrationFormQuery>
{
public Validator()
{
RuleFor(rfq => rfq.Company)
.SetValidator(new CompanyForm.Validator());
RuleFor(rfq => rfq.BranchOffice)
.SetValidator(new BranchOfficeForm.Validator());
RuleFor(rfq => rfq.BranchOffice).NotNull()
.When(rfq => rfq.Company != null);
}
}
}
}

View File

@ -0,0 +1,9 @@
using InternshipSystem.Api.Queries.SearchQuery;
namespace InternshipSystem.Api.Controllers
{
public class BranchOfficeSearchQuery : SearchQuery
{
public string City { get; set; } = "";
}
}

View File

@ -0,0 +1,10 @@
namespace InternshipSystem.Api.Queries.SearchQuery
{
public class CompanySearchQuery : SearchQuery
{
/// <summary>
/// Value against which collection will be queried
/// </summary>
public string Name { get; set; } = "";
}
}

View File

@ -0,0 +1,7 @@
namespace InternshipSystem.Api.Queries.SearchQuery
{
public class EditionSearchQuery : SearchQuery
{
public long? Course { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using InternshipSystem.Core;
namespace InternshipSystem.Api.Queries.SearchQuery
{
public class InternshipSearchQuery : SearchQuery
{
public Guid? EditionId { get; set; } = null;
public DocumentState? InternshipState { get; set; } = null;
public DocumentState? ReportState { get; set; }
public int? StudentAlbumNumber { get; set; } = null;
public string StudentFirstName { get; set; } = "";
public string StudentLastName { get; set; } = "";
/// <summary>
/// SortValue: Date, InternshipState
/// </summary>
public string OrderByField { get; set; } = "";
public SortOrder SortOrder { get; set; } = SortOrder.None;
public DocumentState? DocumentWithState { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using InternshipSystem.Api.Queries.SearchQuery;
namespace InternshipSystem.Api.Controllers
{
public class InternshipTypeSearchQuery : SearchQuery
{
public string Label { get; set; } = "";
public string LabelEng { get; set; } = "";
}
}

View File

@ -0,0 +1,15 @@
namespace InternshipSystem.Api.Queries.SearchQuery
{
public class SearchQuery
{
/// <summary>
/// Which part of the collections to retrieve
/// </summary>
public int Page { get; set; } = 0;
/// <summary>
/// Size of the retrieved part
/// </summary>
public int PerPage { get; set; } = 30;
}
}

View File

@ -0,0 +1,9 @@
namespace InternshipSystem.Api.Queries.SearchQuery
{
public enum SortOrder
{
Asc,
Desc,
None
}
}

View File

@ -0,0 +1,11 @@
using InternshipSystem.Api.Queries.SearchQuery;
namespace InternshipSystem.Api.Controllers
{
public class StudentSearchQuery : SearchQuery
{
public int? AlbumNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}

View File

@ -0,0 +1,36 @@
using FluentValidation;
namespace InternshipSystem.Api.Queries
{
public class StaticPageForm
{
public long? Id { get; set; }
public string AccessName { get; set; }
public string Title { get; set; }
public string TitleEng { get; set; }
public string Content { get; set; }
public string ContentEng { get; set; }
public class Validator : AbstractValidator<StaticPageForm>
{
public Validator()
{
RuleFor(sp => sp.Id).NotNull()
.When(sp =>
string.IsNullOrEmpty(sp.AccessName) || string.IsNullOrEmpty(sp.Title) ||
string.IsNullOrEmpty(sp.TitleEng) || string.IsNullOrEmpty(sp.Content) ||
string.IsNullOrEmpty(sp.ContentEng));
RuleFor(sp => sp.AccessName).NotEmpty()
.When(sp => !sp.Id.HasValue);
RuleFor(sp => sp.Title).NotEmpty()
.When(sp => !sp.Id.HasValue);
RuleFor(sp => sp.TitleEng).NotEmpty()
.When(sp => !sp.Id.HasValue);
RuleFor(sp => sp.Content).NotEmpty()
.When(sp => !sp.Id.HasValue);
RuleFor(sp => sp.ContentEng).NotEmpty()
.When(sp => !sp.Id.HasValue);
}
}
}
}

View File

@ -0,0 +1,21 @@
using FluentValidation;
namespace InternshipSystem.Api.Queries
{
public class StudentForm
{
public int? AlbumNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Course { get; set; }
public int? Semester { get; set; }
public class Validator : AbstractValidator<StudentForm>
{
public Validator()
{
}
}
}
}

View File

@ -0,0 +1,20 @@
using System.Collections.Generic;
using InternshipSystem.Api.Converters;
using Newtonsoft.Json;
namespace InternshipSystem.Api.Controllers
{
public class CasUserData
{
public string AlbumNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[JsonConverter(typeof(StringArrayConverter<string>))]
public List<string> Mail { get; set; }
public string PersonNumber { get; set; }
[JsonConverter(typeof(StringArrayConverter<string>))]
public List<string> Pg_Cui_Portalroles { get; set; }
}
}

View File

@ -0,0 +1,10 @@
namespace InternshipSystem.Api.Controllers
{
public class CasUserProfile
{
public string Service { get; set; }
public CasUserData Attributes { get; set; }
public string Id { get; set; }
public string Client_Id { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity;
using InternshipSystem.Core.Entity.Internship;
using InternshipSystem.Core.UglyOrmArtifacts;
namespace InternshipSystem.Api.Result
{
public class EditionConfigurationResult
{
public List<InternshipSubject> AvailableSubjects { get; set; }
public List<ReportField> ReportSchema { get; set; }
public Course Course { get; set; }
public DateTime EditionStart { get; set; }
public DateTime EditionFinish { get; set; }
public DateTime ReportingStart { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity;
using InternshipSystem.Core.Entity.Internship;
namespace InternshipSystem.Api.Result
{
public class EditionDetailsResult
{
public Guid Id { get; set; }
public DateTime EditionStart { get; set; }
public DateTime EditionFinish { get; set; }
public DateTime ReportingStart { get; set; }
public Course Course { get; set; }
public List<InternshipSubject> AvailableSubjects { get; set; }
public List<InternshipType> AvailableInternshipTypes { get; set; }
public List<ReportField> ReportSchema { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity;
namespace InternshipSystem.Api.Result
{
public class EditionManagementResult
{
public Guid Id { get; set; }
public DateTime EditionStart { get; set; }
public DateTime EditionFinish { get; set; }
public DateTime ReportingStart { get; set; }
public Course Course { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System;
namespace InternshipSystem.Api.Result
{
public class EditionResult
{
public Guid Id { get; set; }
public DateTime EditionStart { get; set; }
public DateTime EditionFinish { get; set; }
public string CourseName { get; set; }
public string Status { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace InternshipSystem.Api.Security
{
public static class InternshipClaims
{
public static string Edition = "Edition";
public static string PersonNumber = "PersonNumber";
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using InternshipSystem.Api.Options;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace InternshipSystem.Api.Security
{
public class JwtTokenService
{
private SecurityOptions _securityOptions;
public JwtTokenService(IOptions<SecurityOptions> securityOptions)
{
_securityOptions = securityOptions.Value;
}
public string generateToken(ClaimsIdentity identity)
{
var handler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_securityOptions.Secret);
var descriptor = new SecurityTokenDescriptor
{
Subject = identity,
Expires = DateTime.UtcNow.AddMinutes(_securityOptions.Expiration),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var jwtToken = handler.CreateJwtSecurityToken(descriptor);
return handler.WriteToken(jwtToken);
}
}
}

View File

@ -0,0 +1,8 @@
namespace InternshipSystem.Api.Security
{
public static class Policies
{
public const string RegisteredOnly = "RegisteredForEditionOnly";
public const string IsOverseer = "IsOverseer";
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace InternshipSystem.Api.Security
{
public class User
{
public long PersonNumber { get; set; }
public string Name { get; set; }
public Guid? EditionId { get; set; }
}
}

View File

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
namespace InternshipSystem.Api.Service
{
public class FileValidator
{
private readonly List<(string Mime, byte[] Signature)> validFileTypes;
public FileValidator()
{
validFileTypes = new List<(string, byte[])> {
("application/pdf", new byte[] { 0x25, 0x50, 0x44, 0x46 }),
("image/jpeg", new byte[] { 0xFF, 0xD8, 0xFF, 0xDB }),
("image/jpeg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 }),
("image/jpeg", new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 })
};
}
public bool IsValidFile(byte[] scan)
{
return IsFileValidType(scan);
}
private bool IsFileValidType(byte[] scan)
{
return GetFileMime(scan) != null;
}
public string GetFileMime(byte[] scan)
{
var header = scan[..4];
return validFileTypes.FirstOrDefault(sig => sig.Signature.SequenceEqual(header)).Mime;
}
}
}

View File

@ -0,0 +1,96 @@
using System;
using System.IO;
using System.Reflection;
using AutoMapper;
using InternshipSystem.Api.Controllers;
using InternshipSystem.Api.Extensions;
using InternshipSystem.Api.ModelBinders;
using InternshipSystem.Api.Options;
using InternshipSystem.Api.Security;
using InternshipSystem.Api.Service;
using InternshipSystem.Repository;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace InternshipSystem.Api
{
public class Startup
{
private IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) =>
Configuration = configuration;
public void ConfigureServices(IServiceCollection services)
{
services
.Configure<SecurityOptions>(Configuration.GetSection("SecurityOptions"));
services
.AddStudentAuthentication()
.AddAuthorization(o =>
{
o.AddPolicy(Policies.RegisteredOnly, policy => policy.RequireClaim("Edition"));
//TODO: change to claim for InternshipRepresentative
o.AddPolicy(Policies.IsOverseer, policy => policy.RequireClaim("PersonNumber"));
})
.AddHttpClient<GutCasClient>();
services
.AddDbContext<InternshipDbContext>(o =>
o.UseNpgsql(Configuration.GetConnectionString("InternshipDatabase")))
.AddScoped<DatabaseFiller>()
.AddScoped<FileValidator>()
.AddScoped<JwtTokenService>()
.AddAutoMapper(cfg => cfg.AddProfile<ApiProfile>());
services
.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo {Title = "InternshipSystem Api - TEST", Version = "v1"});
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
})
.AddSwaggerGenNewtonsoftSupport()
.AddControllers(o => { o.ModelBinderProviders.Insert(0, new UserBinderProvider()); })
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.Converters.Add(new StringEnumConverter());
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseMigration();
if (env.IsDevelopment())
{
app
.UseDefaultData("true".Equals(Environment.GetEnvironmentVariable("FILLER__USE_DEFAULT_DATA")))
.UseDeveloperExceptionPage();
}
app
.UseHttpsRedirection()
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseCors()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
})
.UseSwagger()
.UseSwaggerUI(options => options.SwaggerEndpoint(Path.Join(Configuration.GetValue<string>("ApiPrefix"), "/swagger/v1/swagger.json"), "InternshipSystem Api"));
}
}
}

View File

@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using IdentityServer4.Extensions;
using InternshipSystem.Api.Commands;
using InternshipSystem.Api.Security;
using InternshipSystem.Core;
using InternshipSystem.Core.Entity.Internship;
using InternshipSystem.Core.UglyOrmArtifacts;
using InternshipSystem.Core.ValueObject;
using InternshipSystem.Repository;
using Microsoft.EntityFrameworkCore;
namespace InternshipSystem.Api.UseCases
{
public class UpdateInternshipRegistrationUseCase
{
private readonly InternshipDbContext _dbContext;
private readonly Internship _internship;
private readonly Edition _edition;
private readonly User _user;
private readonly InternshipRegistration subjectRegistration;
public UpdateInternshipRegistrationUseCase(InternshipDbContext dbContext,
Internship internship, Edition edition, User user)
{
_dbContext = dbContext;
_internship = internship;
_edition = edition;
_user = user;
_internship = internship;
subjectRegistration = internship.InternshipRegistration;
}
public async Task<(DocumentState State, IEnumerable<ErrorDescription>)> UpdateInternshipRegistration(
UpdateRegistrationForm registrationCommand,
CancellationToken cancellationToken)
{
UpdateTimeFrame(registrationCommand);
subjectRegistration.DeclaredHours = registrationCommand.Hours ?? subjectRegistration.DeclaredHours;
if (registrationCommand.Type.HasValue)
{
UpdateInternshipType(registrationCommand.Type.Value);
}
if (registrationCommand.Mentor.HasValue)
{
UpdateMentor(registrationCommand.Mentor.Value);
}
if (!registrationCommand.Subjects.IsNullOrEmpty())
{
UpdateSubjects(registrationCommand.Subjects);
}
if (registrationCommand.Company.HasValue)
{
await UpdateCompanyAndBranch(registrationCommand.Company.Value, cancellationToken);
}
return subjectRegistration.ValidateStatus(_edition);
}
private void UpdateTimeFrame(UpdateRegistrationForm registrationCommand)
{
subjectRegistration.Start = registrationCommand.Start ?? subjectRegistration.Start;
subjectRegistration.End = registrationCommand.End ?? subjectRegistration.End;
if (!_edition.IsDateDuringEdition(subjectRegistration.Start, subjectRegistration.End))
{
_internship.AddNewDocument("", DocumentType.OutsideTermApproval);
}
else
{
_internship.RemoveDocument(DocumentType.OutsideTermApproval);
}
}
private void UpdateInternshipType(long typeId)
{
var editionInternshipType = _edition.AvailableInternshipTypes.FirstOrDefault(i => i.InternshipTypeId == typeId);
if (editionInternshipType?.InternshipType.RequireDeansApproval == true)
{
_internship.AddNewDocument("", DocumentType.InternshipTypeApproval);
}
else
{
_internship.RemoveDocument(DocumentType.InternshipTypeApproval);
}
if (editionInternshipType?.InternshipType.RequireInsurance == true)
{
_internship.AddNewDocument("", DocumentType.NnwInsurance);
}
else
{
_internship.RemoveDocument(DocumentType.NnwInsurance);
}
subjectRegistration.Type = editionInternshipType?.InternshipType ?? subjectRegistration.Type;
}
private async Task UpdateCompanyAndBranch(UpdateCompany companyUpdate, CancellationToken cancellationToken)
{
var company = subjectRegistration.Company;
if (companyUpdate.Id.HasValue)
{
company = await _dbContext.Companies
.Include(c => c.Branches)
.FirstAsync(c => c.Id == companyUpdate.Id.Value, cancellationToken);
}
else if (companyUpdate.IsCustomUpdate)
{
company = await _dbContext.Companies
.Include(c => c.Branches)
.SingleOrDefaultAsync(c => c.Provider == _user.PersonNumber, cancellationToken)
?? Company.CreateCompany(companyUpdate.Nip, companyUpdate.Name, _user.PersonNumber);
company.Name = companyUpdate.Name ?? company.Name;
company.Nip = companyUpdate.Nip ?? company.Nip;
subjectRegistration.BranchAddress = null;
}
if (companyUpdate.BranchOffice.HasValue)
{
var branchUpdate = companyUpdate.BranchOffice.Value;
var branch = subjectRegistration.BranchAddress;
if (branchUpdate.Id.HasValue)
{
branch = company.Branches.First(b => b.Id == branchUpdate.Id.Value);
}
else if (branchUpdate.IsCustomUpdate)
{
branch = company.Branches.FirstOrDefault(b => b.Provider == _user.PersonNumber);
if (branch == null)
{
branch = BranchOffice.CreateBranch(branchUpdate.Country, branchUpdate.City, branchUpdate.PostalCode,
branchUpdate.Street, branchUpdate.Building, _user.PersonNumber);
company.AddBranchOffice(branch);
}
branch.Address.Country = branchUpdate.Country ?? branch.Address.Country;
branch.Address.City = branchUpdate.City ?? branch.Address.City;
branch.Address.PostalCode = branchUpdate.PostalCode ?? branch.Address.PostalCode;
branch.Address.Street = branchUpdate.Country ?? branch.Address.Street;
branch.Address.Building = branchUpdate.Building ?? branch.Address.Building;
}
subjectRegistration.BranchAddress = branch;
}
subjectRegistration.Company = company;
}
private void UpdateSubjects(IEnumerable<long> subjects)
{
if (!_edition.AreSubjectsAvailable(subjects))
{
throw new ArgumentException("subjects chosen are not available in this edition");
}
subjectRegistration.Subjects =
subjects
.Select(i => new ProgramSubject
{
Registration = subjectRegistration,
InternshipSubjectId = i
})
.ToList();
}
private void UpdateMentor(UpdateMentor mentorUpdate)
{
subjectRegistration.Mentor ??= new Mentor();
subjectRegistration.Mentor.UpdateInformation(mentorUpdate.FirstName, mentorUpdate.LastName, mentorUpdate.Email, mentorUpdate.PhoneNumber);
}
}
}

View File

@ -0,0 +1,48 @@
using FluentValidation;
using FluentValidation.Validators;
namespace InternshipSystem.Core
{
public class BranchOffice
{
public BranchOffice()
{
}
private BranchOffice(BranchAddress address, long provider)
{
Address = address;
Provider = provider;
}
public long Id { get; set; }
public BranchAddress Address { get; set; }
public long Provider { get; set; }
public static BranchOffice CreateBranch(string country, string city, string postalCode, string street,
string building, long provider = 0)
{
var address = new BranchAddress
{
Building = building,
City = city,
Country = country,
Street = street,
PostalCode = postalCode
};
return new BranchOffice(address, provider);
}
public class Validator : AbstractValidator<BranchOffice>
{
public Validator()
{
RuleFor(x => x.Address)
.SetValidator(new BranchAddress.Validator());
}
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using FluentValidation;
using FluentValidation.Validators;
namespace InternshipSystem.Core
{
public class Company
{
public long Id { get; set; }
public string Nip { get; set; }
public string Name { get; set; }
public List<BranchOffice> Branches { get; set; }
public long Provider { get; set; }
public static Company CreateCompany(string nip, string name, long provider = 0) =>
new Company
{
Nip = nip,
Name = name,
Provider = provider,
Branches = new List<BranchOffice>()
};
public void AddBranchOffice(BranchOffice createBranch)
{
Branches.Add(createBranch);
}
public class Validator : AbstractValidator<Company>
{
public Validator()
{
RuleFor(x => x.Nip)
.NotEmpty()
.WithMessage("error.company.nip.empty");
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("error.company.name.empty");
}
}
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace InternshipSystem.Core.Entity
{
public class Course
{
public long Id { get; set; }
public string Name { get; set; }
public string NameEng { get; set; }
public static Course CreateCourse(string name, string nameEng) =>
new Course
{
Name = name,
NameEng = nameEng,
};
public void UpdateCourse(string name, string nameEng)
{
Name = name;
NameEng = nameEng;
}
}
}

View File

@ -0,0 +1,24 @@
using InternshipSystem.Core.ValueObject;
namespace InternshipSystem.Core
{
public class Document
{
public long Id { get; set; }
public string Description { get; set; }
public DocumentScan Scan { get; set; }
public DocumentType Type { get; set; }
public DocumentState State { get; set; }
public string ChangeStateComment { get; set; }
}
public class DocumentScan
{
public long DocumentId { get; set; }
public Document Document { get; set; }
public long Size { get; set; }
public string Filename { get; set; }
public string Mime { get; set; }
public byte[] File { get; set; }
}
}

View File

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using InternshipSystem.Core.Entity;
using InternshipSystem.Core.Entity.Internship;
using InternshipSystem.Core.UglyOrmArtifacts;
namespace InternshipSystem.Core
{
public class Edition
{
public Guid Id { get; set; }
public DateTime EditionStart { get; set; }
public DateTime EditionFinish { get; set; }
public DateTime ReportingStart { get; set; }
public Course Course { get; set; }
public List<Internship> Internships { get; set; }
public List<EditionSubject> AvailableSubjects { get; set; }
public List<EditionInternshipType> AvailableInternshipTypes { get; set; }
public List<ReportFieldEdition> ReportSchema { get; set; }
public bool IsOpen => EditionFinish < DateTime.Today;
public static Edition CreateEdition(DateTime start, DateTime end, DateTime reportingStart, Course course,
IEnumerable<long> subjectsIds, IEnumerable<long> internshipTypesIds, IEnumerable<long> reportFieldIds)
{
var newEdition = CreateEdition(start, end, reportingStart, course);
newEdition.AvailableSubjects =
subjectsIds
.Select(s => new EditionSubject
{
Edition = newEdition,
InternshipSubjectId = s,
})
.ToList();
newEdition.AvailableInternshipTypes =
internshipTypesIds
.Select(i => new EditionInternshipType
{
Edition = newEdition,
InternshipTypeId = i,
})
.ToList();
newEdition.ReportSchema =
reportFieldIds
.Select(i => new ReportFieldEdition
{
Edition = newEdition,
ReportFieldId = i,
})
.ToList();
return newEdition;
}
public static Edition CreateEdition(DateTime start, DateTime end, DateTime reportingStart, Course course)
{
return new Edition
{
EditionStart = start,
EditionFinish = end,
ReportingStart = reportingStart,
Course = course,
AvailableSubjects = new List<EditionSubject>(),
AvailableInternshipTypes = new List<EditionInternshipType>(),
ReportSchema = new List<ReportFieldEdition>()
};
}
public void UpdateEdition(DateTime? start, DateTime? end, DateTime? reportingStart, Course course,
IEnumerable<long> subjectsIds, IEnumerable<long> internshipTypesIds, IEnumerable<long> reportFieldIds)
{
EditionStart = start ?? EditionStart;
EditionFinish = end ?? EditionFinish;
ReportingStart = reportingStart ?? ReportingStart;
Course = course;
if (subjectsIds != null)
{
AvailableSubjects =
subjectsIds
.Select(s => new EditionSubject
{
InternshipSubjectId = s,
})
.ToList();
}
if (internshipTypesIds != null)
{
AvailableInternshipTypes =
internshipTypesIds
.Select(i => new EditionInternshipType
{
InternshipTypeId = i,
})
.ToList();
}
if (reportFieldIds != null)
{
ReportSchema =
reportFieldIds
.Select(i => new ReportFieldEdition
{
ReportFieldId = i,
})
.ToList();
}
}
public void RegisterInternship(Student student)
{
if (Internships.Any(i => i.Student.Id == student.Id))
{
throw new ArgumentException("error.registration.already_registered");
}
var internship = Internship.CreateStudentsInternship(student);
Internships.Add(internship);
}
public bool IsDateDuringEdition(DateTime start, DateTime end)
{
return start >= EditionStart && end <= EditionFinish;
}
public bool IsTypeAvailable(InternshipType internshipType)
{
return AvailableInternshipTypes.Any(e => e.InternshipType == internshipType);
}
public bool AreSubjectsAvailable(IEnumerable<long> internshipSubjects)
{
return internshipSubjects.All(s => AvailableSubjects.Any(su => su.InternshipSubjectId == s));
}
public bool IsValidDates => EditionStart <= EditionFinish;
}
}

Some files were not shown because too many files have changed in this diff Show More