├── frontend ├── src │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── error │ │ │ ├── error.component.css │ │ │ ├── error.component.html │ │ │ └── error.component.ts │ │ ├── app.component.css │ │ ├── auth │ │ │ ├── user-change │ │ │ │ ├── user-change.component.css │ │ │ │ ├── user-change.component.html │ │ │ │ └── user-change.component.ts │ │ │ ├── login-data.model.ts │ │ │ ├── login │ │ │ │ ├── login.component.css │ │ │ │ ├── login.component.html │ │ │ │ └── login.component.ts │ │ │ ├── register │ │ │ │ ├── register.component.css │ │ │ │ ├── register.component.ts │ │ │ │ └── register.component.html │ │ │ ├── password-change │ │ │ │ ├── password-change.component.css │ │ │ │ ├── password-change.component.html │ │ │ │ └── password-change.component.ts │ │ │ ├── singup-data.model.ts │ │ │ ├── user-info │ │ │ │ ├── user-info.component.css │ │ │ │ ├── user-info.component.html │ │ │ │ └── user-info.component.ts │ │ │ ├── auth-interceptor.ts │ │ │ └── auth.guard.ts │ │ ├── app.component.html │ │ ├── admin │ │ │ ├── add-brand │ │ │ │ ├── add-brand.component.css │ │ │ │ ├── add-brand.component.html │ │ │ │ └── add-brand.component.ts │ │ │ ├── add-color │ │ │ │ ├── add-color.component.css │ │ │ │ ├── add-color.component.html │ │ │ │ └── add-color.component.ts │ │ │ ├── add-model │ │ │ │ ├── add-model.component.css │ │ │ │ ├── add-model.component.html │ │ │ │ └── add-model.component.ts │ │ │ ├── add-fuel-type │ │ │ │ ├── add-fuel-type.component.css │ │ │ │ ├── add-fuel-type.component.html │ │ │ │ └── add-fuel-type.component.ts │ │ │ ├── delete-brand │ │ │ │ ├── delete-brand.component.css │ │ │ │ ├── delete-brand.component.html │ │ │ │ └── delete-brand.component.ts │ │ │ ├── delete-color │ │ │ │ ├── delete-color.component.css │ │ │ │ ├── delete-color.component.html │ │ │ │ └── delete-color.component.ts │ │ │ ├── delete-model │ │ │ │ ├── delete-model.component.css │ │ │ │ ├── delete-model.component.html │ │ │ │ └── delete-model.component.ts │ │ │ ├── delete-fuel-type │ │ │ │ ├── delete-fuel-type.component.css │ │ │ │ ├── delete-fuel-type.component.html │ │ │ │ └── delete-fuel-type.component.ts │ │ │ ├── user.model.ts │ │ │ ├── admin-controller │ │ │ │ ├── admin-controller.component.css │ │ │ │ ├── admin-controller.component.ts │ │ │ │ └── admin-controller.component.html │ │ │ ├── admin.guard.ts │ │ │ └── block.guard.ts │ │ ├── cars │ │ │ ├── my-car-list │ │ │ │ ├── my-car-list.component.css │ │ │ │ ├── my-car-list.component.html │ │ │ │ └── my-car-list.component.ts │ │ │ ├── car-info │ │ │ │ ├── car-info.component.css │ │ │ │ ├── car-info.component.html │ │ │ │ └── car-info.component.ts │ │ │ ├── car-list │ │ │ │ ├── car-list.component.css │ │ │ │ ├── car-list.component.html │ │ │ │ └── car-list.component.ts │ │ │ ├── car.model.ts │ │ │ ├── car-add │ │ │ │ ├── car-add.component.css │ │ │ │ ├── mime-type.validator.ts │ │ │ │ ├── car-add.component.html │ │ │ │ └── car-add.component.ts │ │ │ └── car-edit │ │ │ │ ├── car-edit.component.css │ │ │ │ ├── car-edit.component.html │ │ │ │ └── car-edit.component.ts │ │ ├── header │ │ │ ├── header.component.css │ │ │ ├── header.component.html │ │ │ └── header.component.ts │ │ ├── app.component.ts │ │ ├── error-interceptor.ts │ │ ├── app.component.spec.ts │ │ ├── app-routing.module.ts │ │ └── app.module.ts │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── styles.css │ ├── main.ts │ ├── index.html │ ├── test.ts │ └── polyfills.ts ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json ├── Dockerfile ├── proxy.conf.json ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── .browserslistrc ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json ├── karma.conf.js └── angular.json ├── Help.txt ├── backend ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── images │ ├── DFSA-1652971451117.jpg │ ├── Kola-1653827891183.jpg │ ├── Naslov-1652972000104.jpg │ ├── Naslov-1652972775969.jpg │ ├── Naslov-1653054410766.jpg │ ├── Naslov-1653057759230.jpg │ ├── Naslov-1653058110955.jpg │ ├── Naslov-1653058449353.jpg │ ├── Panda-1653058231027.jpg │ ├── Panda-1653058898472.jpg │ ├── Title-1653053637843.jpg │ ├── gfsg-1652972113316.jpg │ ├── Prodajem-1652955989112.jpg │ ├── gfdsgfds-1652971161907.jpg │ ├── gfdsgfds-1652971714824.jpg │ ├── Novi-oglas-1653052175772.jpg │ ├── Novi-oglas-1653318899344.jpg │ ├── Novi-oglas-1653830026992.jpg │ ├── Novi-oglas-1653835888533.jpg │ ├── Novi-oglas-1654003539349.jpg │ ├── NoviNaslov-1653058626287.jpg │ ├── Adminov-oglas-1653830074177.jpg │ ├── Petrov-oglas-1653830127651.jpg │ ├── Prodajem-auto-1652955234395.jpg │ ├── Prodajem-kola-1652863307285.jpg │ ├── Prodajem-kola-1652889206499.jpg │ ├── Prodajem-kola-1652890933463.jpg │ ├── Prodajem-kola-1652950723346.jpg │ ├── Prodajem-kola-1652961098101.jpg │ ├── Prodajem-kola-1652971001829.jpg │ ├── Prodajem-kola-1653037293270.jpg │ ├── Prodajem-kola-1653040178738.jpg │ ├── Prodajem-kola-1653055903221.jpg │ ├── Prodajem-kola-1653059206527.jpg │ ├── Prodajem-kola-1653306923249.jpg │ ├── Prodajem-kola-1653319268195.jpg │ ├── Prodajem-kola-1653827679860.jpg │ ├── Prodajem-kola-1654002707628.jpg │ ├── Prodajem-golfa-1653827732012.jpg │ ├── Prodajem-golfa-1654002970902.jpg │ └── Drugi-Petrov-oglas-1653830168207.jpg ├── Dockerfile ├── src │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── app │ │ │ └── backend │ │ │ └── BackendApplicationTests.java │ └── main │ │ ├── java │ │ └── com │ │ │ └── app │ │ │ └── backend │ │ │ ├── models │ │ │ ├── LoginData.java │ │ │ ├── SignupData.java │ │ │ ├── FrontUser.java │ │ │ ├── FrontCar.java │ │ │ ├── Role.java │ │ │ ├── Brand.java │ │ │ ├── Color.java │ │ │ ├── Fuel.java │ │ │ ├── Model.java │ │ │ ├── Car.java │ │ │ └── User.java │ │ │ ├── repositories │ │ │ ├── FuelRepository.java │ │ │ ├── RoleRepository.java │ │ │ ├── BrandRepository.java │ │ │ ├── ColorRepository.java │ │ │ ├── UserRepository.java │ │ │ ├── ModelRepository.java │ │ │ └── CarRepository.java │ │ │ ├── util │ │ │ ├── CarUrlName.java │ │ │ ├── CarResponse.java │ │ │ └── FileUploadUtil.java │ │ │ ├── AdditionalResourceWebConfiguration.java │ │ │ ├── services │ │ │ ├── CarService.java │ │ │ ├── AdminService.java │ │ │ ├── UserService.java │ │ │ ├── AdminServiceImpl.java │ │ │ ├── UserServiceImpl.java │ │ │ └── CarServiceImpl.java │ │ │ ├── security │ │ │ ├── CorsConfig.java │ │ │ └── SecurityConfig.java │ │ │ ├── filters │ │ │ ├── CustomAuthorizationFilter.java │ │ │ └── CustomAuthenticationFilter.java │ │ │ └── controllers │ │ │ ├── CarController.java │ │ │ └── UserController.java │ │ └── resources │ │ └── application.properties ├── .gitignore ├── docker-compose.yml └── pom.xml └── docker-compose.yml /frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/error/error.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Help.txt: -------------------------------------------------------------------------------- 1 | The ports in the application are adapted to work in docker containers! -------------------------------------------------------------------------------- /frontend/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | main { 2 | width: 80%; 3 | margin: 1rem auto; 4 | } -------------------------------------------------------------------------------- /frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/src/app/auth/user-change/user-change.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field, 2 | textarea { 3 | width: 100%; 4 | } -------------------------------------------------------------------------------- /frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
-------------------------------------------------------------------------------- /frontend/src/app/auth/login-data.model.ts: -------------------------------------------------------------------------------- 1 | export interface LoginData { 2 | username: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/frontend/src/favicon.ico -------------------------------------------------------------------------------- /frontend/src/app/auth/login/login.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/auth/register/register.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/admin/add-brand/add-brand.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/admin/add-color/add-color.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/admin/add-model/add-model.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/cars/my-car-list/my-car-list.component.css: -------------------------------------------------------------------------------- 1 | .no-cars { 2 | text-align: center; 3 | font-size: 40px; 4 | margin: 100px; 5 | } -------------------------------------------------------------------------------- /frontend/src/app/admin/add-fuel-type/add-fuel-type.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-brand/delete-brand.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-color/delete-color.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-model/delete-model.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/auth/password-change/password-change.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /backend/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /backend/images/DFSA-1652971451117.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/DFSA-1652971451117.jpg -------------------------------------------------------------------------------- /backend/images/Kola-1653827891183.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Kola-1653827891183.jpg -------------------------------------------------------------------------------- /backend/images/Naslov-1652972000104.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Naslov-1652972000104.jpg -------------------------------------------------------------------------------- /backend/images/Naslov-1652972775969.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Naslov-1652972775969.jpg -------------------------------------------------------------------------------- /backend/images/Naslov-1653054410766.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Naslov-1653054410766.jpg -------------------------------------------------------------------------------- /backend/images/Naslov-1653057759230.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Naslov-1653057759230.jpg -------------------------------------------------------------------------------- /backend/images/Naslov-1653058110955.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Naslov-1653058110955.jpg -------------------------------------------------------------------------------- /backend/images/Naslov-1653058449353.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Naslov-1653058449353.jpg -------------------------------------------------------------------------------- /backend/images/Panda-1653058231027.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Panda-1653058231027.jpg -------------------------------------------------------------------------------- /backend/images/Panda-1653058898472.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Panda-1653058898472.jpg -------------------------------------------------------------------------------- /backend/images/Title-1653053637843.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Title-1653053637843.jpg -------------------------------------------------------------------------------- /backend/images/gfsg-1652972113316.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/gfsg-1652972113316.jpg -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-fuel-type/delete-fuel-type.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } 4 | 5 | mat-spinner { 6 | margin: auto; 7 | } -------------------------------------------------------------------------------- /backend/images/Prodajem-1652955989112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-1652955989112.jpg -------------------------------------------------------------------------------- /backend/images/gfdsgfds-1652971161907.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/gfdsgfds-1652971161907.jpg -------------------------------------------------------------------------------- /backend/images/gfdsgfds-1652971714824.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/gfdsgfds-1652971714824.jpg -------------------------------------------------------------------------------- /backend/images/Novi-oglas-1653052175772.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Novi-oglas-1653052175772.jpg -------------------------------------------------------------------------------- /backend/images/Novi-oglas-1653318899344.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Novi-oglas-1653318899344.jpg -------------------------------------------------------------------------------- /backend/images/Novi-oglas-1653830026992.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Novi-oglas-1653830026992.jpg -------------------------------------------------------------------------------- /backend/images/Novi-oglas-1653835888533.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Novi-oglas-1653835888533.jpg -------------------------------------------------------------------------------- /backend/images/Novi-oglas-1654003539349.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Novi-oglas-1654003539349.jpg -------------------------------------------------------------------------------- /backend/images/NoviNaslov-1653058626287.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/NoviNaslov-1653058626287.jpg -------------------------------------------------------------------------------- /frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /backend/images/Adminov-oglas-1653830074177.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Adminov-oglas-1653830074177.jpg -------------------------------------------------------------------------------- /backend/images/Petrov-oglas-1653830127651.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Petrov-oglas-1653830127651.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-auto-1652955234395.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-auto-1652955234395.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1652863307285.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1652863307285.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1652889206499.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1652889206499.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1652890933463.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1652890933463.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1652950723346.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1652950723346.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1652961098101.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1652961098101.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1652971001829.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1652971001829.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1653037293270.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1653037293270.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1653040178738.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1653040178738.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1653055903221.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1653055903221.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1653059206527.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1653059206527.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1653306923249.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1653306923249.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1653319268195.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1653319268195.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1653827679860.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1653827679860.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-kola-1654002707628.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-kola-1654002707628.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-golfa-1653827732012.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-golfa-1653827732012.jpg -------------------------------------------------------------------------------- /backend/images/Prodajem-golfa-1654002970902.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Prodajem-golfa-1654002970902.jpg -------------------------------------------------------------------------------- /backend/images/Drugi-Petrov-oglas-1653830168207.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Simple-Car-Sales-App.Angular-java.project/HEAD/backend/images/Drugi-Petrov-oglas-1653830168207.jpg -------------------------------------------------------------------------------- /frontend/src/app/admin/user.model.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: number; 3 | firstName: string; 4 | lastName: string; 5 | city: string; 6 | country: string; 7 | banned: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk11:alpine-jre 2 | WORKDIR /app 3 | RUN mkdir -p images 4 | COPY target/backend-0.0.1-SNAPSHOT.jar backend.jar 5 | ENTRYPOINT ["java","-jar","/app/backend.jar"] 6 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest as builder 2 | 3 | RUN mkdir -p /app 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | 9 | RUN npm install 10 | RUN npm run build --prod 11 | 12 | #CMD ["npm", "start"] 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/app/admin/admin-controller/admin-controller.component.css: -------------------------------------------------------------------------------- 1 | ul { 2 | list-style: none; 3 | padding: 0; 4 | margin: 0; 5 | display: flex; 6 | } 7 | 8 | li { 9 | margin: 10px; 10 | } -------------------------------------------------------------------------------- /frontend/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, body { height: 100%; } 4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } 5 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-info/car-info.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field, 2 | textarea { 3 | width: 100%; 4 | } 5 | 6 | mat-spinner { 7 | margin: auto; 8 | } 9 | 10 | img { 11 | width: 100%; 12 | max-width: 200px; 13 | } -------------------------------------------------------------------------------- /frontend/src/app/auth/singup-data.model.ts: -------------------------------------------------------------------------------- 1 | export interface SignupData { 2 | firstName: string; 3 | lastName: string; 4 | username: string; 5 | password: string; 6 | timestamp: number; 7 | city: string; 8 | country: string; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/app/error/error.component.html: -------------------------------------------------------------------------------- 1 |

Greska!

2 |
3 |

{{ data.message }}

4 |
5 |
6 | 7 |
-------------------------------------------------------------------------------- /frontend/src/app/auth/user-info/user-info.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field, 2 | textarea { 3 | width: 100%; 4 | } 5 | 6 | ul { 7 | list-style: none; 8 | padding: 0; 9 | margin: 0; 10 | display: flex; 11 | } 12 | 13 | li { 14 | margin: 10px; 15 | } -------------------------------------------------------------------------------- /backend/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /frontend/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/server": { 3 | "target": "http://localhost:8080", 4 | "secure": false, 5 | "changeOrigin": true, 6 | "logLevel": "debug", 7 | "pathRewrite": { 8 | "^/server": "" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /frontend/src/app/header/header.component.css: -------------------------------------------------------------------------------- 1 | ul { 2 | list-style: none; 3 | padding: 0; 4 | margin: 0; 5 | } 6 | 7 | a { 8 | text-decoration: none; 9 | color: white; 10 | } 11 | 12 | ul { 13 | display: flex; 14 | } 15 | 16 | .spacer { 17 | flex: 1 1 auto; 18 | } -------------------------------------------------------------------------------- /backend/src/test/java/com/app/backend/BackendApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.app.backend; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class BackendApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-list/car-list.component.css: -------------------------------------------------------------------------------- 1 | mat-spinner { 2 | margin: auto; 3 | } 4 | 5 | mat-paginator { 6 | margin-top: 1rem; 7 | } 8 | 9 | img { 10 | width: 100%; 11 | max-width: 200px; 12 | } 13 | 14 | .no-cars { 15 | text-align: center; 16 | font-size: 40px; 17 | margin: 100px; 18 | } -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/LoginData.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class LoginData { 11 | String username; 12 | String password; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/repositories/FuelRepository.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.app.backend.models.Fuel; 6 | 7 | public interface FuelRepository extends JpaRepository { 8 | Fuel findByName(String name); 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/repositories/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.app.backend.models.Role; 6 | 7 | public interface RoleRepository extends JpaRepository { 8 | Role findByName(String name); 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/repositories/BrandRepository.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.app.backend.models.Brand; 6 | 7 | public interface BrandRepository extends JpaRepository { 8 | Brand findByName(String name); 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/repositories/ColorRepository.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.app.backend.models.Color; 6 | 7 | public interface ColorRepository extends JpaRepository { 8 | Color findByName(String name); 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/repositories/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.app.backend.models.User; 6 | 7 | public interface UserRepository extends JpaRepository { 8 | User findByUsername(String username); 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car.model.ts: -------------------------------------------------------------------------------- 1 | export interface Car { 2 | id: number; 3 | title: string; 4 | brand: string; 5 | model: string; 6 | mileage: number; 7 | registration: number; 8 | price: number; 9 | fuel: string; 10 | color: string; 11 | phone: string; 12 | content: string; 13 | path: string; 14 | owner_id: number; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-add/car-add.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field, 2 | textarea { 3 | width: 100%; 4 | } 5 | 6 | mat-spinner { 7 | margin: auto; 8 | } 9 | 10 | input[type="file"] { 11 | visibility: hidden; 12 | } 13 | 14 | .image-preview { 15 | height: 5rem; 16 | margin: 1rem 0; 17 | } 18 | 19 | .image-preview img { 20 | height: 100%; 21 | } -------------------------------------------------------------------------------- /frontend/src/app/cars/car-edit/car-edit.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field, 2 | textarea { 3 | width: 100%; 4 | } 5 | 6 | mat-spinner { 7 | margin: auto; 8 | } 9 | 10 | input[type="file"] { 11 | visibility: hidden; 12 | } 13 | 14 | .image-preview { 15 | height: 5rem; 16 | margin: 1rem 0; 17 | } 18 | 19 | .image-preview img { 20 | height: 100%; 21 | } -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/util/CarUrlName.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.util; 2 | 3 | import com.app.backend.models.Car; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class CarUrlName { 13 | private Car car; 14 | private String fileName; 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/util/CarResponse.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.util; 2 | 3 | import java.util.List; 4 | 5 | import com.app.backend.models.Car; 6 | 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class CarResponse { 15 | private List cars; 16 | private Long count; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /frontend/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost:3306/database 2 | spring.datasource.username=root 3 | spring.datasource.password=Redstar98 4 | 5 | spring.jpa.hibernate.ddl-auto=create 6 | spring.jpa.show-sql=true 7 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 8 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect 9 | spring.jpa.properties.hibernate.format_sql=true -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/SignupData.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class SignupData { 11 | String firstName; 12 | String lastName; 13 | String username; 14 | String password; 15 | Long timestamp; 16 | String city; 17 | String country; 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/FrontUser.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class FrontUser { 11 | private Long id; 12 | private String firstName; 13 | private String lastName; 14 | private Long timestamp; 15 | private String city; 16 | private String country; 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/repositories/ModelRepository.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.repositories; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import com.app.backend.models.Brand; 8 | import com.app.backend.models.Model; 9 | 10 | public interface ModelRepository extends JpaRepository { 11 | Model findByName(String name); 12 | List findByBrand(Brand brand); 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/app/error/error.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, OnInit } from '@angular/core'; 2 | import { MAT_DIALOG_DATA } from '@angular/material/dialog'; 3 | 4 | @Component({ 5 | templateUrl: './error.component.html', 6 | styleUrls: ['./error.component.css'] 7 | }) 8 | export class ErrorComponent implements OnInit { 9 | 10 | constructor(@Inject(MAT_DIALOG_DATA) public data: {message: string}) { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AuthService } from './auth/auth.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'] 8 | }) 9 | export class AppComponent implements OnInit { 10 | title = 'frontend'; 11 | 12 | constructor(private authService: AuthService) {} 13 | 14 | ngOnInit(): void { 15 | console.log("I ovo se radi") 16 | this.authService.autoAuthUser(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /frontend/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/FrontCar.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class FrontCar { 11 | private Long id; 12 | private String title; 13 | private String brand; 14 | private String model; 15 | private Long mileage; 16 | private Long registration; 17 | private Long price; 18 | private String fuel; 19 | private String color; 20 | private String phone; 21 | private String content; 22 | private String path; 23 | private Long owner_id; 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/AdditionalResourceWebConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.app.backend; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | @Configuration 8 | public class AdditionalResourceWebConfiguration implements WebMvcConfigurer { 9 | @Override 10 | public void addResourceHandlers(final ResourceHandlerRegistry registry) { 11 | registry.addResourceHandler("/images/**") 12 | .addResourceLocations("file:images/"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AutoMarket 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/Role.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | 8 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 9 | 10 | import lombok.AllArgsConstructor; 11 | import lombok.Data; 12 | import lombok.NoArgsConstructor; 13 | 14 | @Entity 15 | @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 16 | @Data 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class Role { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.AUTO) 22 | private Long id; 23 | private String name; 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/app/auth/auth-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http"; 2 | import { Injectable } from "@angular/core"; 3 | import { Observable } from "rxjs"; 4 | import { AuthService } from "./auth.service"; 5 | 6 | @Injectable() 7 | export class AuthInterceptor implements HttpInterceptor { 8 | constructor(private authService: AuthService) {} 9 | 10 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 11 | const authToken = this.authService.getToken(); 12 | const authRequest = req.clone({ 13 | headers: req.headers.set("Authorization","Bearer " + authToken) 14 | }); 15 | return next.handle(authRequest); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/Brand.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | 9 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 10 | 11 | import lombok.AllArgsConstructor; 12 | import lombok.Data; 13 | import lombok.NoArgsConstructor; 14 | 15 | @Entity 16 | @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 17 | @Data 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class Brand { 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.AUTO) 23 | private Long id; 24 | @Column(unique=true) 25 | private String name; 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/Color.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | 9 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 10 | 11 | import lombok.AllArgsConstructor; 12 | import lombok.Data; 13 | import lombok.NoArgsConstructor; 14 | 15 | @Entity 16 | @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 17 | @Data 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class Color { 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.AUTO) 23 | private Long id; 24 | @Column(unique=true) 25 | private String name; 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/Fuel.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | 9 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 10 | 11 | import lombok.AllArgsConstructor; 12 | import lombok.Data; 13 | import lombok.NoArgsConstructor; 14 | 15 | @Entity 16 | @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 17 | @Data 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class Fuel { 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.AUTO) 23 | private Long id; 24 | @Column(unique=true) 25 | private String name; 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/app/admin/add-color/add-color.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Dodaj boju 8 | 9 | 10 |
11 | 12 | 13 | 14 | Molim unesite boju. 15 | 16 | 17 |
18 | 19 |
20 |
-------------------------------------------------------------------------------- /frontend/src/app/auth/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from "@angular/router"; 3 | import { Observable } from "rxjs"; 4 | import { AuthService } from "./auth.service"; 5 | 6 | @Injectable() 7 | export class AuthGuard implements CanActivate { 8 | 9 | constructor(private authService: AuthService, private router: Router) {} 10 | 11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable | Promise { 12 | const isAuth: boolean = this.authService.getIsAuth(); 13 | if (!isAuth) { 14 | this.router.navigate(['/login']); 15 | } 16 | return isAuth; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/app/admin/admin.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from "@angular/router"; 3 | import { Observable } from "rxjs"; 4 | import { AuthService } from "../auth/auth.service"; 5 | 6 | @Injectable() 7 | export class AdminGuard implements CanActivate { 8 | 9 | constructor(private authService: AuthService, private router: Router) {} 10 | 11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable | Promise { 12 | const isAdmin: boolean = this.authService.getIsAdmin(); 13 | if (!isAdmin) { 14 | this.router.navigate(['/']); 15 | } 16 | return isAdmin; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/app/admin/block.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from "@angular/router"; 3 | import { Observable } from "rxjs"; 4 | import { AuthService } from "../auth/auth.service"; 5 | 6 | @Injectable() 7 | export class BlockGuard implements CanActivate { 8 | 9 | constructor(private authService: AuthService, private router: Router) {} 10 | 11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable | Promise { 12 | const isBlocked: boolean = this.authService.getIsBlocked(); 13 | if (isBlocked) { 14 | this.router.navigate(['/']); 15 | } 16 | return !isBlocked; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/app/admin/add-brand/add-brand.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Dodaj marku automobila 8 | 9 | 10 |
11 | 12 | 13 | 14 | Molim unesite marku automobila. 15 | 16 | 17 |
18 | 19 |
20 |
-------------------------------------------------------------------------------- /frontend/src/app/admin/add-fuel-type/add-fuel-type.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Dodaj vrstu goriva 8 | 9 | 10 |
11 | 12 | 13 | 14 | Molim unesite tip goriva automobila. 15 | 16 | 17 |
18 | 19 |
20 |
-------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/repositories/CarRepository.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.repositories; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | 9 | import com.app.backend.models.Brand; 10 | import com.app.backend.models.Car; 11 | import com.app.backend.models.Color; 12 | import com.app.backend.models.Fuel; 13 | import com.app.backend.models.Model; 14 | import com.app.backend.models.User; 15 | 16 | public interface CarRepository extends JpaRepository { 17 | Page findByOwner(User owner, Pageable pageable); 18 | List findByBrand(Brand brand); 19 | List findByModel(Model model); 20 | List findByFuel(Fuel fuel); 21 | List findByColor(Color color); 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/services/CarService.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.services; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.web.multipart.MultipartFile; 6 | 7 | import com.app.backend.models.Car; 8 | import com.app.backend.models.FrontCar; 9 | import com.app.backend.models.User; 10 | import com.app.backend.util.CarResponse; 11 | 12 | public interface CarService { 13 | Car addCar(MultipartFile image, String carData, User user) throws IOException; 14 | Car getCar(Long id); 15 | CarResponse getCars(Integer page, Integer pagesize); 16 | CarResponse getMyCars(User user, Integer page, Integer pagesize); 17 | Car updateCarImage(Long id, MultipartFile image, String carData, User user) throws IOException; 18 | Car updateCarNoImage(Long id, FrontCar frontCar, User user); 19 | Long deleteCar(Long id, User user); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/security/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.security; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | public class CorsConfig { 10 | @Bean 11 | public WebMvcConfigurer corsConfigurer() { 12 | return new WebMvcConfigurer() { 13 | @Override 14 | public void addCorsMappings(CorsRegistry registry) { 15 | registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedOrigins("*") 16 | .allowedHeaders("*") 17 | .exposedHeaders("*"); 18 | } 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-color/delete-color.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Obrisi boju 8 | 9 | 10 |
11 | 12 | 13 | 14 | {{color}} 15 | 16 | 17 | 18 | Molim iazberite boju. 19 | 20 | 21 |
22 | 23 |
24 |
-------------------------------------------------------------------------------- /frontend/src/app/admin/delete-brand/delete-brand.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Obrisi marku automobila 8 | 9 | 10 |
11 | 12 | 13 | 14 | {{brand}} 15 | 16 | 17 | 18 | Molim iazberite marku automobila. 19 | 20 | 21 |
22 | 23 |
24 |
-------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/Model.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.FetchType; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import javax.persistence.ManyToOne; 10 | 11 | import com.fasterxml.jackson.annotation.JsonIgnore; 12 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 13 | 14 | import lombok.AllArgsConstructor; 15 | import lombok.Data; 16 | import lombok.NoArgsConstructor; 17 | 18 | @Entity 19 | @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 20 | @Data 21 | @NoArgsConstructor 22 | @AllArgsConstructor 23 | public class Model { 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.AUTO) 26 | private Long id; 27 | @Column(unique=true) 28 | private String name; 29 | @ManyToOne(fetch = FetchType.EAGER) 30 | @JsonIgnore 31 | private Brand brand; 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/app/admin/add-brand/add-brand.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { AdminService } from '../admin.service'; 4 | 5 | @Component({ 6 | selector: 'app-add-brand', 7 | templateUrl: './add-brand.component.html', 8 | styleUrls: ['./add-brand.component.css'] 9 | }) 10 | export class AddBrandComponent implements OnInit { 11 | form!: FormGroup; 12 | isLoading: boolean = false; 13 | 14 | constructor(private adminService: AdminService) { } 15 | 16 | ngOnInit(): void { 17 | this.isLoading = false; 18 | this.form = new FormGroup({ 19 | brand: new FormControl(null, { 20 | validators: [Validators.required] 21 | }) 22 | }); 23 | } 24 | 25 | addBrand() { 26 | if (this.form.invalid) { 27 | return; 28 | } 29 | this.isLoading = true; 30 | this.adminService.addBrand(this.form.value.brand); 31 | this.form.reset(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/app/admin/add-color/add-color.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { AdminService } from '../admin.service'; 4 | 5 | @Component({ 6 | selector: 'app-add-color', 7 | templateUrl: './add-color.component.html', 8 | styleUrls: ['./add-color.component.css'] 9 | }) 10 | export class AddColorComponent implements OnInit { 11 | form!: FormGroup; 12 | isLoading: boolean = false; 13 | 14 | constructor(private adminService: AdminService) { } 15 | 16 | ngOnInit(): void { 17 | this.isLoading = false; 18 | this.form = new FormGroup({ 19 | color: new FormControl(null, { 20 | validators: [Validators.required] 21 | }) 22 | }); 23 | } 24 | 25 | addColor() { 26 | if (this.form.invalid) { 27 | return; 28 | } 29 | this.isLoading = true; 30 | this.adminService.addColor(this.form.value.color); 31 | this.form.reset(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-fuel-type/delete-fuel-type.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Obrisi vrstu goriva 8 | 9 | 10 |
11 | 12 | 13 | 14 | {{fuelType}} 15 | 16 | 17 | 18 | Molim izaberite tip goriva automobila. 19 | 20 | 21 |
22 | 23 |
24 |
-------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2017", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/services/AdminService.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.services; 2 | 3 | import java.util.List; 4 | 5 | import com.app.backend.models.Brand; 6 | import com.app.backend.models.Color; 7 | import com.app.backend.models.Fuel; 8 | import com.app.backend.models.Model; 9 | import com.app.backend.models.User; 10 | 11 | public interface AdminService { 12 | User blockUser(Long userId); 13 | User unblockUser(Long userId); 14 | List getUsers(User user); 15 | Long adminDeleteCar(Long carId); 16 | List getBrands(); 17 | Brand saveBrand(String brandName); 18 | Long deleteBrand(String brandName); 19 | List getModels(String brandName); 20 | Model addModelToBrand(String brandName, String modelName); 21 | Long deleteModelFromBrand(String brandName, String modelName); 22 | List getFuelTypes(); 23 | Fuel saveFuel(String fuelType); 24 | Long deleteFuel(String fuelType); 25 | List getColors(); 26 | Color saveColor(String colorName); 27 | Long deleteColor(String colorName); 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/app/admin/add-fuel-type/add-fuel-type.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { AdminService } from '../admin.service'; 4 | 5 | @Component({ 6 | selector: 'app-add-fuel-type', 7 | templateUrl: './add-fuel-type.component.html', 8 | styleUrls: ['./add-fuel-type.component.css'] 9 | }) 10 | export class AddFuelTypeComponent implements OnInit { 11 | form!: FormGroup; 12 | isLoading: boolean = false; 13 | 14 | constructor(private adminService: AdminService) { } 15 | 16 | ngOnInit(): void { 17 | this.isLoading = false; 18 | this.form = new FormGroup({ 19 | fuel: new FormControl(null, { 20 | validators: [Validators.required] 21 | }) 22 | }); 23 | } 24 | 25 | addFuelType() { 26 | if (this.form.invalid) { 27 | return; 28 | } 29 | this.isLoading = true; 30 | this.adminService.addFuelType(this.form.value.fuel); 31 | this.form.reset(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/services/UserService.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.services; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import com.app.backend.models.Role; 7 | import com.app.backend.models.SignupData; 8 | import com.app.backend.models.User; 9 | 10 | public interface UserService { 11 | boolean hasAdminPrivilege(String authorizationHeader); 12 | boolean isBlocked(Long userId); 13 | User getUserById(Long id); 14 | User getUser(String username); 15 | User getUserFromAuthorizationHeader(String authorizationHeader); 16 | User signupUser(SignupData signupData); 17 | User changePassword(Long userId, String oldPassword, String newPassword); 18 | User updateUser(User user, String firstName, String lastName, Long timestamp, String city, String country); 19 | Boolean logoutUser(HttpServletRequest request, HttpServletResponse response); 20 | User saveUser(User user); 21 | Role saveRole(Role role); 22 | void addRoleToUser(String username, String roleName); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/app/cars/my-car-list/my-car-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ car.brand }} 6 | 7 |
8 | 9 |
10 |

{{ car.model }}

11 | 12 | EDIT 13 | 14 | 15 |
16 |
17 | 18 |

Nemate oglasa!

-------------------------------------------------------------------------------- /frontend/src/app/error-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http"; 2 | import { Injectable } from "@angular/core"; 3 | import { MatDialog } from "@angular/material/dialog"; 4 | import { catchError, Observable, throwError } from "rxjs"; 5 | import { ErrorComponent } from "./error/error.component"; 6 | 7 | @Injectable() 8 | export class ErrorInterceptor implements HttpInterceptor { 9 | 10 | constructor(private dialog: MatDialog) {} 11 | 12 | intercept(req: HttpRequest, next: HttpHandler): Observable> { 13 | return next.handle(req).pipe( 14 | catchError((error: HttpErrorResponse) => { 15 | // console.log(error); 16 | // alert(error.error.message) 17 | let errorMessage: string = "An unknown error occured!"; 18 | if (error.error.message) { 19 | errorMessage = error.error.message 20 | } 21 | this.dialog.open(ErrorComponent, {data: {message: errorMessage}}); 22 | return throwError(error); 23 | }) 24 | ); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /frontend/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /backend/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | mysqldb: 4 | image: mysql:8 5 | restart: unless-stopped 6 | environment: 7 | - MYSQL_ROOT_PASSWORD=Redstar98 8 | - MYSQL_DATABASE=database 9 | ports: 10 | - 3307:3306 11 | healthcheck: 12 | test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] 13 | timeout: 20s 14 | retries: 10 15 | app: 16 | depends_on: 17 | mysqldb: 18 | condition: service_healthy 19 | build: 20 | context: ./ 21 | dockerfile: Dockerfile 22 | restart: on-failure 23 | ports: 24 | - 8081:8080 25 | volumes: 26 | - ./images:/app/images 27 | environment: 28 | SPRING_APPLICATION_JSON: '{ 29 | "spring.datasource.url" : "jdbc:mysql://mysqldb:3306/database?allowPublicKeyRetrieval=true&useSSL=false", 30 | "spring.datasource.username" : "root", 31 | "spring.datasource.password" : "Redstar98", 32 | "spring.jpa.properties.hibernate.dialect" : "org.hibernate.dialect.MySQL8Dialect", 33 | "spring.jpa.hibernate.ddl-auto" : "update" 34 | }' 35 | stdin_open: true 36 | tty: true 37 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.2.5. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /frontend/src/app/auth/login/login.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Prijava korisnika 4 | 5 |
6 | 7 |
8 | 9 | 10 | 11 | Polje mora biti popunjeno! 12 | 13 | 14 | Molim unesite ispravnu e-mail adresu. 15 | 16 | 17 | 18 | 19 | 20 | Polje mora biti popunjeno! 21 | 22 | 23 | 24 |
25 |
-------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/util/FileUploadUtil.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | import java.nio.file.StandardCopyOption; 9 | 10 | import org.springframework.web.multipart.MultipartFile; 11 | 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | @Slf4j 15 | public class FileUploadUtil { 16 | public static void saveFile(String uploadDir, String fileName, MultipartFile file) throws IOException { 17 | Path uploadPath = Paths.get(uploadDir); 18 | log.info("Sadrzaj gde cuva: {}", uploadPath.toString()); 19 | if (!Files.exists(uploadPath)) { 20 | Files.createDirectories(uploadPath); 21 | } 22 | 23 | try (InputStream inputStream = file.getInputStream()) { 24 | Path filePath = uploadPath.resolve(fileName); 25 | log.info("Sacuvana je slika!, {}", filePath.toString()); 26 | Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING); 27 | } catch (IOException ioe) { 28 | throw new IOException("Could not save image file: " + fileName, ioe); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | }); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'frontend'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('frontend'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement as HTMLElement; 33 | expect(compiled.querySelector('.content span')?.textContent).toContain('frontend app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/Car.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.FetchType; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import javax.persistence.ManyToOne; 9 | 10 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 11 | 12 | import lombok.AllArgsConstructor; 13 | import lombok.Data; 14 | import lombok.NoArgsConstructor; 15 | 16 | @Entity 17 | @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 18 | @Data 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class Car { 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.AUTO) 24 | private Long id; 25 | private String title; 26 | @ManyToOne(fetch = FetchType.EAGER) 27 | private Brand brand; 28 | @ManyToOne(fetch = FetchType.EAGER) 29 | private Model model; 30 | private Long mileage; 31 | private Long registration; 32 | private Long price; 33 | @ManyToOne(fetch = FetchType.EAGER) 34 | private Fuel fuel; 35 | @ManyToOne(fetch = FetchType.EAGER) 36 | private Color color; 37 | private String phone; 38 | private String content; 39 | private String path; 40 | @ManyToOne(fetch = FetchType.EAGER) 41 | private User owner; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/app/admin/add-model/add-model.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Dodaj model automobila 8 | 9 | 10 |
11 | 12 | 13 | 14 | {{brand}} 15 | 16 | 17 | 18 | Molim iazberite marku automobila. 19 | 20 | 21 | 22 | 23 | 24 | Molim unesite model automobila. 25 | 26 | 27 |
28 | 29 |
30 |
-------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/models/User.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.models; 2 | 3 | import java.time.LocalDate; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.FetchType; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import javax.persistence.ManyToMany; 13 | 14 | import com.fasterxml.jackson.annotation.JsonIgnore; 15 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 16 | 17 | import lombok.AllArgsConstructor; 18 | import lombok.Data; 19 | import lombok.NoArgsConstructor; 20 | 21 | @Entity 22 | @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 23 | @Data 24 | @NoArgsConstructor 25 | @AllArgsConstructor 26 | public class User { 27 | @Id 28 | @GeneratedValue(strategy = GenerationType.AUTO) 29 | private Long id; 30 | private String firstName; 31 | private String lastName; 32 | @JsonIgnore 33 | private String username; 34 | @JsonIgnore 35 | private String password; 36 | @JsonIgnore 37 | private LocalDate brithdate; 38 | private String city; 39 | private String country; 40 | private Boolean banned; 41 | @ManyToMany(fetch = FetchType.EAGER) 42 | @JsonIgnore 43 | private Collection roles = new ArrayList<>(); 44 | } 45 | -------------------------------------------------------------------------------- /frontend/src/app/auth/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Subscription } from 'rxjs'; 4 | import { AuthService } from '../auth.service'; 5 | 6 | @Component({ 7 | //selector: 'app-login', 8 | templateUrl: './login.component.html', 9 | styleUrls: ['./login.component.css'] 10 | }) 11 | export class LoginComponent implements OnInit, OnDestroy { 12 | form!: FormGroup; 13 | isLoading = false; 14 | private authStatusSub!: Subscription; 15 | 16 | constructor(public authService: AuthService) { } 17 | 18 | ngOnInit(): void { 19 | this.form = new FormGroup({ 20 | email: new FormControl(null, [Validators.required, Validators.email]), 21 | password: new FormControl(null, [Validators.required]) 22 | }); 23 | this.authStatusSub = this.authService.getAuthStatusListener().subscribe( 24 | authStatus => { 25 | this.isLoading = false; 26 | } 27 | ); 28 | } 29 | 30 | onLogin() { 31 | if(this.form.invalid) { 32 | return; 33 | } 34 | this.isLoading = true; 35 | this.authService.login(this.form.value.email, this.form.value.password); 36 | this.form.reset(); 37 | } 38 | 39 | ngOnDestroy(): void { 40 | this.authStatusSub.unsubscribe(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/app/admin/admin-controller/admin-controller.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | import { AdminService } from '../admin.service'; 4 | import { User } from '../user.model'; 5 | 6 | @Component({ 7 | selector: 'app-admin-controller', 8 | templateUrl: './admin-controller.component.html', 9 | styleUrls: ['./admin-controller.component.css'] 10 | }) 11 | export class AdminControllerComponent implements OnInit, OnDestroy { 12 | isLoading: boolean = false; 13 | users: User[] = []; 14 | displayedColumns: string[] = ['firstName', 'lastName', 'userInfo', 'userOptions']; 15 | private usersSub!: Subscription; 16 | 17 | constructor(private adminService: AdminService) { } 18 | 19 | ngOnInit(): void { 20 | this.isLoading = false; 21 | this.adminService.getUsers(); 22 | this.usersSub = this.adminService.getUsersUpdateListener() 23 | .subscribe((usersData: {users: User[]}) => { 24 | this.users = usersData.users; 25 | }); 26 | this.adminService.getUsers(); 27 | } 28 | 29 | blockUser(userId: number) { 30 | this.adminService.blockUser(userId); 31 | } 32 | 33 | unblockUser(userId: number) { 34 | this.adminService.unblockUser(userId); 35 | } 36 | 37 | ngOnDestroy(): void { 38 | this.usersSub.unsubscribe(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/app/auth/password-change/password-change.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Promena lozinke 8 | 9 |
10 | 11 |
12 | 13 | 14 | 15 | Polje mora biti popunjeno! 16 | 17 | 18 | 19 | 20 | 21 | Polje mora biti popunjeno! 22 | 23 | 24 | Nije dobra forma lozinke! 25 | 26 | 27 | 28 |
29 |
-------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | mysqldb: 4 | image: mysql:8 5 | restart: unless-stopped 6 | environment: 7 | - MYSQL_ROOT_PASSWORD=Redstar98 8 | - MYSQL_DATABASE=database 9 | ports: 10 | - 3307:3306 11 | healthcheck: 12 | test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] 13 | timeout: 20s 14 | retries: 10 15 | app: 16 | depends_on: 17 | mysqldb: 18 | condition: service_healthy 19 | build: 20 | context: ./backend 21 | dockerfile: Dockerfile 22 | restart: on-failure 23 | ports: 24 | - 8081:8080 25 | volumes: 26 | - ./images:/app/images 27 | environment: 28 | SPRING_APPLICATION_JSON: '{ 29 | "spring.datasource.url" : "jdbc:mysql://mysqldb:3306/database?allowPublicKeyRetrieval=true&useSSL=false", 30 | "spring.datasource.username" : "root", 31 | "spring.datasource.password" : "Redstar98", 32 | "spring.jpa.properties.hibernate.dialect" : "org.hibernate.dialect.MySQL8Dialect", 33 | "spring.jpa.hibernate.ddl-auto" : "update" 34 | }' 35 | stdin_open: true 36 | tty: true 37 | angular-service: 38 | container_name: angular-container 39 | build: 40 | context: ./frontend 41 | dockerfile: Dockerfile 42 | ports: 43 | - 4201:4200 44 | links: 45 | - app 46 | command: > 47 | bash -c "npm start" 48 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --host 0.0.0.0", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "~13.2.0", 14 | "@angular/cdk": "^13.2.6", 15 | "@angular/common": "~13.2.0", 16 | "@angular/compiler": "~13.2.0", 17 | "@angular/core": "~13.2.0", 18 | "@angular/forms": "~13.2.0", 19 | "@angular/material": "^13.2.6", 20 | "@angular/platform-browser": "~13.2.0", 21 | "@angular/platform-browser-dynamic": "~13.2.0", 22 | "@angular/router": "~13.2.0", 23 | "rxjs": "~7.5.0", 24 | "tslib": "^2.3.0", 25 | "zone.js": "~0.11.4" 26 | }, 27 | "devDependencies": { 28 | "@angular-devkit/build-angular": "~13.2.5", 29 | "@angular/cli": "~13.2.5", 30 | "@angular/compiler-cli": "~13.2.0", 31 | "@types/jasmine": "~3.10.0", 32 | "@types/node": "^12.11.1", 33 | "jasmine-core": "~4.0.0", 34 | "karma": "~6.3.0", 35 | "karma-chrome-launcher": "~3.1.0", 36 | "karma-coverage": "~2.1.0", 37 | "karma-jasmine": "~4.0.0", 38 | "karma-jasmine-html-reporter": "~1.7.0", 39 | "typescript": "~4.5.2" 40 | } 41 | } -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-brand/delete-brand.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Subscription } from 'rxjs'; 4 | import { AdminService } from '../admin.service'; 5 | 6 | @Component({ 7 | selector: 'app-delete-brand', 8 | templateUrl: './delete-brand.component.html', 9 | styleUrls: ['./delete-brand.component.css'] 10 | }) 11 | export class DeleteBrandComponent implements OnInit, OnDestroy { 12 | form!: FormGroup; 13 | isLoading: boolean = false; 14 | brands: string[] = []; 15 | private brandsSub!: Subscription; 16 | 17 | constructor(private adminService: AdminService) { } 18 | 19 | ngOnInit(): void { 20 | this.adminService.getBrands(); 21 | this.brandsSub = this.adminService.getBrandsUpdateListener() 22 | .subscribe((brandsData: {brands: string[]}) => { 23 | this.brands = brandsData.brands; 24 | }); 25 | this.isLoading = false; 26 | this.form = new FormGroup({ 27 | brand: new FormControl(null, { 28 | validators: [Validators.required] 29 | }) 30 | }); 31 | } 32 | 33 | deleteBrand() { 34 | if (this.form.invalid) { 35 | return; 36 | } 37 | this.isLoading = true; 38 | this.adminService.deleteBrand(this.form.value.brand); 39 | this.form.reset(); 40 | } 41 | 42 | ngOnDestroy(): void { 43 | this.brandsSub.unsubscribe(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-color/delete-color.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Subscription } from 'rxjs'; 4 | import { AdminService } from '../admin.service'; 5 | 6 | @Component({ 7 | selector: 'app-delete-color', 8 | templateUrl: './delete-color.component.html', 9 | styleUrls: ['./delete-color.component.css'] 10 | }) 11 | export class DeleteColorComponent implements OnInit, OnDestroy { 12 | form!: FormGroup; 13 | isLoading: boolean = false; 14 | colors: string[] = []; 15 | private colorsSub!: Subscription; 16 | 17 | constructor(private adminService: AdminService) { } 18 | 19 | ngOnInit(): void { 20 | this.adminService.getColors(); 21 | this.colorsSub = this.adminService.getColorsUpdateListener() 22 | .subscribe((colorsData: {colors: string[]}) => { 23 | this.colors = colorsData.colors; 24 | }); 25 | this.isLoading = false; 26 | this.form = new FormGroup({ 27 | color: new FormControl(null, { 28 | validators: [Validators.required] 29 | }) 30 | }); 31 | } 32 | 33 | deleteColor() { 34 | if (this.form.invalid) { 35 | return; 36 | } 37 | this.isLoading = true; 38 | this.adminService.deleteColor(this.form.value.color); 39 | this.form.reset(); 40 | } 41 | 42 | ngOnDestroy(): void { 43 | this.colorsSub.unsubscribe(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/app/auth/password-change/password-change.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { ActivatedRoute, ParamMap } from '@angular/router'; 4 | import { AuthService } from '../auth.service'; 5 | 6 | @Component({ 7 | selector: 'app-password-change', 8 | templateUrl: './password-change.component.html', 9 | styleUrls: ['./password-change.component.css'] 10 | }) 11 | export class PasswordChangeComponent implements OnInit { 12 | form!: FormGroup; 13 | isLoading = false; 14 | userId!: number; 15 | 16 | constructor(private authService: AuthService, private route: ActivatedRoute) { } 17 | 18 | ngOnInit(): void { 19 | this.isLoading = false; 20 | this.form = new FormGroup({ 21 | oldPassword: new FormControl(null, [Validators.required]), 22 | newPassword: new FormControl( 23 | null, 24 | [ 25 | Validators.required, 26 | Validators.pattern("^(?=.*[A-Z])(?=.*\\d)[a-zA-Z].{6,}$") 27 | ] 28 | ) 29 | }); 30 | this.route.paramMap.subscribe((paramMap: ParamMap) => { 31 | this.userId = Number(paramMap.get('userId')); 32 | }); 33 | } 34 | 35 | onChangePassword() { 36 | if (this.form.invalid) { 37 | return; 38 | } 39 | this.isLoading = true; 40 | this.authService.changePassword(this.userId, this.form.value.oldPassword, this.form.value.newPassword); 41 | this.form.reset(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-model/delete-model.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Obrisi model automobila 8 | 9 | 10 |
11 | 12 | 13 | 14 | {{brand}} 15 | 16 | 17 | 18 | Molim iazberite marku automobila. 19 | 20 | 21 | 22 | 23 | 24 | {{model}} 25 | 26 | 27 | 28 | Molim izaberite model automobila. 29 | 30 | 31 |
32 | 33 |
34 |
-------------------------------------------------------------------------------- /frontend/src/app/admin/add-model/add-model.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Subscription } from 'rxjs'; 4 | import { AdminService } from '../admin.service'; 5 | 6 | @Component({ 7 | selector: 'app-add-model', 8 | templateUrl: './add-model.component.html', 9 | styleUrls: ['./add-model.component.css'] 10 | }) 11 | export class AddModelComponent implements OnInit, OnDestroy { 12 | form!: FormGroup; 13 | isLoading: boolean = false; 14 | brands: string[] = []; 15 | private brandsSub!: Subscription; 16 | 17 | constructor(private adminService: AdminService) { } 18 | 19 | ngOnInit(): void { 20 | this.adminService.getBrands(); 21 | this.brandsSub = this.adminService.getBrandsUpdateListener() 22 | .subscribe((brandsData: {brands: string[]}) => { 23 | this.brands = brandsData.brands; 24 | }); 25 | this.isLoading = false; 26 | this.form = new FormGroup({ 27 | brand: new FormControl(null, { 28 | validators: [Validators.required] 29 | }), 30 | model: new FormControl(null, { 31 | validators: [Validators.required] 32 | }) 33 | }); 34 | } 35 | 36 | addModel() { 37 | if (this.form.invalid) { 38 | return; 39 | } 40 | this.isLoading = true; 41 | this.adminService.addModel(this.form.value.brand, this.form.value.model); 42 | this.form.reset(); 43 | } 44 | 45 | ngOnDestroy(): void { 46 | this.brandsSub.unsubscribe(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-add/mime-type.validator.ts: -------------------------------------------------------------------------------- 1 | import { AbstractControl } from "@angular/forms"; 2 | import { Observable, Observer, of } from "rxjs"; 3 | 4 | export const mimeType = (control: AbstractControl): Promise<{[key: string]: any} | any> | Observable<{[key: string]: any} | any> => { 5 | if (typeof(control.value) === 'string') { 6 | return of(null); 7 | } 8 | const file = control.value as File; 9 | const fileReader = new FileReader(); 10 | const frObs = Observable.create((observer: Observer<{[key: string]: any} | any>) => { 11 | fileReader.addEventListener("loadend", () => { 12 | const arr = new Uint8Array(fileReader.result as ArrayBuffer).subarray(0, 4); 13 | let header = ""; 14 | let isValid = false; 15 | for (let i = 0; i < arr.length; i++) { 16 | header += arr[i].toString(16); 17 | } 18 | switch (header) { 19 | case "89504e47": 20 | isValid = true; 21 | break; 22 | case "ffd8ffe0": 23 | case "ffd8ffe1": 24 | case "ffd8ffe2": 25 | case "ffd8ffe3": 26 | case "ffd8ffe8": 27 | isValid = true; 28 | break; 29 | default: 30 | isValid = false; // Or you can use the blob.type as fallback 31 | break; 32 | } 33 | if(isValid) { 34 | observer.next(null); 35 | } else { 36 | observer.next({invalidMimeType: true}); 37 | } 38 | observer.complete(); 39 | }); 40 | fileReader.readAsArrayBuffer(file); 41 | }); 42 | return frObs; 43 | }; 44 | -------------------------------------------------------------------------------- /frontend/src/app/admin/delete-fuel-type/delete-fuel-type.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Subscription } from 'rxjs'; 4 | import { CarsService } from 'src/app/cars/cars.service'; 5 | import { AdminService } from '../admin.service'; 6 | 7 | @Component({ 8 | selector: 'app-delete-fuel-type', 9 | templateUrl: './delete-fuel-type.component.html', 10 | styleUrls: ['./delete-fuel-type.component.css'] 11 | }) 12 | export class DeleteFuelTypeComponent implements OnInit, OnDestroy { 13 | form!: FormGroup; 14 | isLoading: boolean = false; 15 | fuelTypes: string[] = []; 16 | private fuelTypesSub!: Subscription; 17 | 18 | constructor(private adminService: AdminService) { } 19 | 20 | 21 | ngOnInit(): void { 22 | this.adminService.getFuelTypes(); 23 | this.fuelTypesSub = this.adminService.getFuelTypesUpdateListener() 24 | .subscribe((fuelTypesData: {fuelTypes: string[]}) => { 25 | this.fuelTypes = fuelTypesData.fuelTypes; 26 | }); 27 | this.isLoading = false; 28 | this.form = new FormGroup({ 29 | fuel: new FormControl(null, { 30 | validators: [Validators.required] 31 | }) 32 | }); 33 | } 34 | 35 | deleteFuelType() { 36 | if (this.form.invalid) { 37 | return; 38 | } 39 | this.isLoading = true; 40 | this.adminService.deleteFuelType(this.form.value.fuel); 41 | this.form.reset(); 42 | } 43 | 44 | ngOnDestroy(): void { 45 | this.fuelTypesSub.unsubscribe(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/frontend'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /frontend/src/app/header/header.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | SVI OGLASI 4 | 5 | 6 | 7 | Administrator Vas je blokirao! 8 | 9 | 10 | 35 | -------------------------------------------------------------------------------- /frontend/src/app/auth/user-info/user-info.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Informacije o korisniku 4 | 5 | 6 |
7 | 8 | Ime 9 | 10 | 11 | 12 | Prezime 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Mesto stanovanja 22 | 23 | 24 | 25 | Drzava stanovanja 26 | 27 | 28 |
    29 |
  • 30 | 31 |
  • 32 |
  • 33 | 34 |
  • 35 |
36 |
37 |
-------------------------------------------------------------------------------- /frontend/src/app/admin/delete-model/delete-model.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Subscription } from 'rxjs'; 4 | import { AdminService } from '../admin.service'; 5 | 6 | @Component({ 7 | selector: 'app-delete-model', 8 | templateUrl: './delete-model.component.html', 9 | styleUrls: ['./delete-model.component.css'] 10 | }) 11 | export class DeleteModelComponent implements OnInit, OnDestroy { 12 | form!: FormGroup; 13 | isLoading: boolean = false; 14 | brands: string[] = []; 15 | models: string[] = []; 16 | private brandsSub!: Subscription; 17 | private modelsSub!: Subscription; 18 | 19 | constructor(private adminService: AdminService) { } 20 | 21 | ngOnInit(): void { 22 | this.adminService.getBrands(); 23 | this.brandsSub = this.adminService.getBrandsUpdateListener() 24 | .subscribe((brandsData: {brands: string[]}) => { 25 | this.brands = brandsData.brands; 26 | }); 27 | this.modelsSub = this.adminService.getModelsUpdateListener() 28 | .subscribe((modelsData: {models: string[]}) => { 29 | this.models = modelsData.models; 30 | }); 31 | this.isLoading = false; 32 | this.form = new FormGroup({ 33 | brand: new FormControl(null, { 34 | validators: [Validators.required] 35 | }), 36 | model: new FormControl(null, { 37 | validators: [Validators.required] 38 | }) 39 | }); 40 | } 41 | 42 | deleteModel() { 43 | if (this.form.invalid) { 44 | return; 45 | } 46 | this.isLoading = true; 47 | this.adminService.deleteModel(this.form.value.brand, this.form.value.model); 48 | this.form.reset(); 49 | } 50 | 51 | onChange(brandName: string) { 52 | this.adminService.getModels(brandName); 53 | } 54 | 55 | ngOnDestroy(): void { 56 | this.brandsSub.unsubscribe(); 57 | this.modelsSub.unsubscribe(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/app/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Subscription } from 'rxjs'; 4 | import { AuthService } from '../auth/auth.service'; 5 | 6 | @Component({ 7 | selector: 'app-header', 8 | templateUrl: './header.component.html', 9 | styleUrls: ['./header.component.css'] 10 | }) 11 | export class HeaderComponent implements OnInit, OnDestroy { 12 | userIsAuthenticated: boolean = false; 13 | userIsAdmin: boolean = false; 14 | userIsBlocked: boolean = false; 15 | userId!: number; 16 | private authStatusSub!: Subscription; 17 | private adminStatusSub!: Subscription; 18 | private blockedStatusSub!: Subscription; 19 | 20 | constructor(private authService: AuthService, private router: Router) { } 21 | 22 | ngOnInit(): void { 23 | this.userId = this.authService.getUserId(); 24 | this.userIsAuthenticated = this.authService.getIsAuth(); 25 | this.authStatusSub = this.authService.getAuthStatusListener() 26 | .subscribe(isAuthenticated => { 27 | this.userIsAuthenticated = isAuthenticated; 28 | this.userId = this.authService.getUserId(); 29 | }); 30 | this.userIsAdmin = this.authService.getIsAdmin(); 31 | this.adminStatusSub = this.authService.getAdminStatusListener() 32 | .subscribe(isAdmin => { 33 | this.userIsAdmin = isAdmin; 34 | }); 35 | this.userIsBlocked = this.authService.getIsBlocked(); 36 | this.blockedStatusSub = this.authService.getBlockedStatusListener() 37 | .subscribe(isBlocked => { 38 | this.userIsBlocked = isBlocked; 39 | }) 40 | } 41 | 42 | userInfo() { 43 | this.userId = this.authService.getUserId(); 44 | if (this.userId) { 45 | this.router.navigate(['/userInfo', this.userId]); 46 | } 47 | } 48 | 49 | onLogout() { 50 | this.authService.logout(); 51 | } 52 | 53 | ngOnDestroy(): void { 54 | this.authStatusSub.unsubscribe(); 55 | this.adminStatusSub.unsubscribe(); 56 | this.blockedStatusSub.unsubscribe(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-list/car-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Naslov 5 | {{element.title}} 6 | 7 | 8 | Brend 9 | {{element.brand}} 10 | 11 | 12 | Model 13 | {{element.model}} 14 | 15 | 16 | Cena 17 | {{element.price}} 18 | 19 | 20 | Informacije 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 | 34 | 35 |
36 | 37 |

Nema oglasa!

-------------------------------------------------------------------------------- /frontend/src/app/auth/user-change/user-change.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Azuriraj podatke o korisniku 8 | 9 | 10 |
11 | 12 | 13 | 14 | Molim unesite ime. 15 | 16 | 17 | 18 | 19 | 20 | Molim unesite prezime. 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Molim unesite datum rodjenja 29 | 30 | 31 | 32 | 33 | 34 | Molim unesite mesto stanovanja. 35 | 36 | 37 | 38 | 39 | 40 | Molim unesite drzavu stanovanja. 41 | 42 | 43 | 44 |
45 |
-------------------------------------------------------------------------------- /frontend/src/app/auth/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, NgForm, Validators } from '@angular/forms'; 3 | import { Subscription } from 'rxjs'; 4 | import { AuthService } from '../auth.service'; 5 | 6 | import { DateAdapter } from '@angular/material/core'; 7 | 8 | @Component({ 9 | selector: 'app-register', 10 | templateUrl: './register.component.html', 11 | styleUrls: ['./register.component.css'] 12 | }) 13 | export class RegisterComponent implements OnInit { 14 | form!: FormGroup; 15 | isLoading = false; 16 | private authStatusSub!: Subscription; 17 | 18 | constructor(public authService: AuthService, private dateAdapter: DateAdapter) { 19 | this.dateAdapter.setLocale('en-GB'); //dd/MM/yyyy 20 | } 21 | 22 | ngOnInit(): void { 23 | this.form = new FormGroup({ 24 | firstName: new FormControl(null, [Validators.required]), 25 | lastName: new FormControl(null, [Validators.required]), 26 | email: new FormControl(null, [Validators.required, Validators.email]), 27 | password: new FormControl( 28 | null, 29 | [ 30 | Validators.required, 31 | Validators.pattern("^(?=.*[A-Z])(?=.*\\d)[a-zA-Z].{6,}$") 32 | ] 33 | ), 34 | birthdate: new FormControl(null, [Validators.required]), 35 | city: new FormControl(null, [Validators.required]), 36 | country: new FormControl(null, [Validators.required]) 37 | }); 38 | this.authStatusSub = this.authService.getAuthStatusListener().subscribe( 39 | authStatus => { 40 | this.isLoading = false; 41 | } 42 | ); 43 | } 44 | 45 | onRegister() { 46 | if (this.form.invalid) { 47 | return; 48 | } 49 | this.isLoading = true; 50 | let birthdate: Date = new Date(this.form.value.birthdate); 51 | this.authService.createUser(this.form.value.firstName, this.form.value.lastName, this.form.value.email, 52 | this.form.value.password,birthdate, this.form.value.city, this.form.value.country); 53 | this.form.reset(); 54 | } 55 | 56 | ngOnDestroy(): void { 57 | this.authStatusSub.unsubscribe(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/app/auth/user-change/user-change.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { DateAdapter } from '@angular/material/core'; 4 | import { ActivatedRoute, ParamMap } from '@angular/router'; 5 | import { AuthService } from '../auth.service'; 6 | 7 | @Component({ 8 | selector: 'app-user-change', 9 | templateUrl: './user-change.component.html', 10 | styleUrls: ['./user-change.component.css'] 11 | }) 12 | export class UserChangeComponent implements OnInit { 13 | isLoading: boolean = false; 14 | form!: FormGroup; 15 | userId!: number; 16 | 17 | constructor(private authService: AuthService, private route: ActivatedRoute, private dateAdapter: DateAdapter) { 18 | this.dateAdapter.setLocale('en-GB'); //dd/MM/yyyy 19 | } 20 | 21 | ngOnInit(): void { 22 | this.isLoading = false; 23 | this.form = new FormGroup({ 24 | firstName: new FormControl(null, [Validators.required]), 25 | lastName: new FormControl(null, [Validators.required]), 26 | birthdate: new FormControl(null, [Validators.required]), 27 | city: new FormControl(null, [Validators.required]), 28 | country: new FormControl(null, [Validators.required]) 29 | }); 30 | this.route.paramMap.subscribe((paramMap: ParamMap) => { 31 | this.userId = Number(paramMap.get('userId')); 32 | this.isLoading = true; 33 | this.authService.getUserById(this.userId) 34 | .subscribe(userData => { 35 | this.isLoading = false; 36 | this.form.setValue({ 37 | firstName: userData.firstName, 38 | lastName: userData.lastName, 39 | birthdate: new Date(userData.timestamp), 40 | city: userData.city, 41 | country: userData.country 42 | }); 43 | }); 44 | }); 45 | } 46 | 47 | onUpdateUser() { 48 | if (this.form.invalid) { 49 | return; 50 | } 51 | this.isLoading = true; 52 | this.authService.updateUser(this.userId, this.form.value.firstName, this.form.value.lastName, 53 | this.form.value.birthdate, this.form.value.city, this.form.value.country); 54 | this.form.reset(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/app/auth/user-info/user-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup } from '@angular/forms'; 3 | import { DateAdapter } from '@angular/material/core'; 4 | import { ActivatedRoute, ParamMap } from '@angular/router'; 5 | import { Subscription } from 'rxjs'; 6 | import { AdminService } from 'src/app/admin/admin.service'; 7 | import { AuthService } from '../auth.service'; 8 | 9 | @Component({ 10 | selector: 'app-user-info', 11 | templateUrl: './user-info.component.html', 12 | styleUrls: ['./user-info.component.css'] 13 | }) 14 | export class UserInfoComponent implements OnInit, OnDestroy { 15 | isLoading: boolean = false; 16 | form!: FormGroup; 17 | userId!: number; 18 | loggedUserId!: number; 19 | private authStatusSub!: Subscription; 20 | 21 | constructor(private authService: AuthService, private route: ActivatedRoute, private dateAdapter: DateAdapter) { 22 | this.dateAdapter.setLocale('en-GB'); //dd/MM/yyyy 23 | } 24 | 25 | ngOnInit(): void { 26 | this.authStatusSub = this.authService.getAuthStatusListener().subscribe( 27 | authStatus => { 28 | this.isLoading = false; 29 | this.loggedUserId = this.authService.getUserId(); 30 | } 31 | ); 32 | this.isLoading = false; 33 | this.loggedUserId = this.authService.getUserId(); 34 | this.form = new FormGroup({ 35 | firstName: new FormControl(null, []), 36 | lastName: new FormControl(null, []), 37 | birthdate: new FormControl(null, []), 38 | city: new FormControl(null, []), 39 | country: new FormControl(null, []) 40 | }); 41 | this.route.paramMap.subscribe((paramMap: ParamMap) => { 42 | this.userId = Number(paramMap.get('userId')); 43 | this.isLoading = true; 44 | this.authService.getUserById(this.userId) 45 | .subscribe(userData => { 46 | this.isLoading = false; 47 | this.form.setValue({ 48 | firstName: userData.firstName, 49 | lastName: userData.lastName, 50 | birthdate: new Date(userData.timestamp), 51 | city: userData.city, 52 | country: userData.country 53 | }); 54 | }); 55 | }); 56 | } 57 | 58 | ngOnDestroy(): void { 59 | this.authStatusSub.unsubscribe(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-list/car-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { PageEvent } from '@angular/material/paginator'; 3 | import { Subscription } from 'rxjs'; 4 | import { AuthService } from 'src/app/auth/auth.service'; 5 | import { Car } from '../car.model'; 6 | import { CarsService } from '../cars.service'; 7 | 8 | @Component({ 9 | selector: 'app-car-list', 10 | templateUrl: './car-list.component.html', 11 | styleUrls: ['./car-list.component.css'] 12 | }) 13 | export class CarListComponent implements OnInit, OnDestroy { 14 | cars: Car[] = []; 15 | isLoading: boolean = false; 16 | totalCars = 0; 17 | carsPerPage = 2; 18 | currentPage = 0; 19 | pageSizeOptions = [1, 2, 5, 10, 20]; 20 | userIsAuthenticated: boolean = false; 21 | userId!: number; 22 | displayedColumns: string[] = ['title', 'brand', 'model', 'price', 'carInfo', 'path']; 23 | private carsSub!: Subscription; 24 | private authStatusSub!: Subscription; 25 | 26 | constructor(public carsService: CarsService, private authService: AuthService) { } 27 | 28 | ngOnInit(): void { 29 | this.isLoading = true; 30 | this.carsService.getCars(this.carsPerPage, this.currentPage); 31 | this.userId = this.authService.getUserId(); 32 | this.carsSub = this.carsService.getCarsUpdateListener() 33 | .subscribe((carData: {cars: Car[], carCount: number}) => { 34 | this.isLoading = false; 35 | this.totalCars = carData.carCount; 36 | this.cars = carData.cars; 37 | }); 38 | this.userIsAuthenticated = this.authService.getIsAuth(); 39 | this.authStatusSub = this.authService.getAuthStatusListener() 40 | .subscribe(isAuthenticated => { 41 | this.userIsAuthenticated = isAuthenticated; 42 | this.userId = this.authService.getUserId(); 43 | }); 44 | } 45 | 46 | onChangedPage(pageData: PageEvent) { 47 | this.isLoading = true; 48 | this.currentPage = pageData.pageIndex; 49 | this.carsPerPage = pageData.pageSize; 50 | this.carsService.getCars(this.carsPerPage, this.currentPage); 51 | } 52 | 53 | onDelete(carId: number) { 54 | this.isLoading = true; 55 | this.carsService.deleteCar(carId) 56 | .subscribe({ 57 | next: response => { 58 | this.carsService.getCars(this.carsPerPage, this.currentPage); 59 | }, 60 | error: error => { 61 | this.isLoading = false; 62 | } 63 | }); 64 | } 65 | 66 | ngOnDestroy(): void { 67 | this.carsSub.unsubscribe(); 68 | this.authStatusSub.unsubscribe(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-info/car-info.component.html: -------------------------------------------------------------------------------- 1 | 5 |
6 | 10 |
11 | 12 | 13 | Novi oglas 14 | 15 | 16 |
17 | 18 | Naslov oglasa 19 | 20 | 21 | 22 | Marka automobila 23 | 24 | 25 | 26 | Model automobila 27 | 28 | 29 |
30 | 31 | Kilometraza 32 | 33 | 34 | 35 | Godiste 36 | 37 | 38 | 39 | Cena 40 | 41 | 42 | 43 | Gorivo 44 | 45 | 46 | 47 | Boja 48 | 49 | 50 | 51 | Broj telefona 52 | 53 | 54 | 55 | Opis oglasa 56 | 57 | 58 |
59 |
60 |

Vlasnik:

61 | {{ ownerName }} 62 |
63 |
-------------------------------------------------------------------------------- /frontend/src/app/cars/my-car-list/my-car-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | import { Car } from '../car.model'; 4 | import { CarsService } from '../cars.service'; 5 | import { AuthService } from 'src/app/auth/auth.service'; 6 | import { PageEvent } from '@angular/material/paginator'; 7 | 8 | @Component({ 9 | selector: 'app-my-car-list', 10 | templateUrl: './my-car-list.component.html', 11 | styleUrls: ['./my-car-list.component.css'] 12 | }) 13 | export class MyCarListComponent implements OnInit, OnDestroy { 14 | myCars: Car[] = []; 15 | isLoading: boolean = false; 16 | totalCars = 0; 17 | carsPerPage = 2; 18 | currentPage = 0; 19 | pageSizeOptions = [1, 2, 5, 10, 20]; 20 | userIsAuthenticated: boolean = false; 21 | userId!: number; 22 | private myCarsSub!: Subscription; 23 | private authStatusSub!: Subscription; 24 | 25 | constructor(public carsService: CarsService, public authService: AuthService) { } 26 | 27 | ngOnInit(): void { 28 | this.isLoading = true; 29 | this.userId = this.authService.getUserId(); 30 | this.carsService.getMyCars(this.userId, this.carsPerPage, this.currentPage); 31 | this.myCarsSub = this.carsService.getMyCarsUpdateListener() 32 | .subscribe((myCarData: {cars: Car[], carCount: number}) => { 33 | this.isLoading = false; 34 | this.totalCars = myCarData.carCount; 35 | this.myCars = myCarData.cars; 36 | }); 37 | this.userIsAuthenticated = this.authService.getIsAuth(); 38 | this.authStatusSub = this.authService.getAuthStatusListener() 39 | .subscribe(isAuthenticated => { 40 | this.userIsAuthenticated = isAuthenticated; 41 | this.userId = this.authService.getUserId(); 42 | }); 43 | } 44 | 45 | onChangedPage(pageData: PageEvent) { 46 | this.isLoading = true; 47 | this.currentPage = pageData.pageIndex; 48 | this.carsPerPage = pageData.pageSize; 49 | this.userId = this.authService.getUserId(); 50 | this.carsService.getMyCars(this.userId, this.carsPerPage, this.currentPage); 51 | } 52 | 53 | onDelete(carId: number) { 54 | this.isLoading = true; 55 | this.carsService.deleteCar(carId) 56 | .subscribe({ 57 | next: response => { 58 | console.log(response); 59 | this.carsService.getMyCars(this.userId, this.carsPerPage, this.currentPage); 60 | }, 61 | error: error => { 62 | console.log(error); 63 | this.isLoading = false; 64 | } 65 | }); 66 | } 67 | 68 | ngOnDestroy(): void { 69 | this.myCarsSub.unsubscribe(); 70 | this.authStatusSub.unsubscribe(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /backend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.6.4 10 | 11 | 12 | com.app 13 | backend 14 | 0.0.1-SNAPSHOT 15 | jar 16 | backend 17 | Demo project for Spring Boot 18 | 19 | 11 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-jpa 29 | 30 | 31 | mysql 32 | mysql-connector-java 33 | 34 | 35 | org.apache.tika 36 | tika-core 37 | 1.24.1 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-security 43 | 44 | 45 | com.auth0 46 | java-jwt 47 | 3.18.1 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-devtools 52 | runtime 53 | true 54 | 55 | 56 | org.projectlombok 57 | lombok 58 | true 59 | 60 | 61 | org.springframework.security 62 | spring-security-test 63 | test 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-test 69 | test 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-maven-plugin 78 | 79 | 80 | 81 | org.projectlombok 82 | lombok 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /frontend/src/app/auth/register/register.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Registracija korisnika 4 | 5 |
6 | 7 |
8 | 9 | 10 | 11 | Molim unesite ime. 12 | 13 | 14 | 15 | 16 | 17 | Molim unesite prezime. 18 | 19 | 20 | 21 | 22 | 23 | Polje mora biti popunjeno! 24 | 25 | 26 | Molim unesite ispravnu e-mail adresu. 27 | 28 | 29 | 30 | 31 | 32 | Polje mora biti popunjeno! 33 | 34 | 35 | Nije dobra forma lozinke! 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Molim unesite datum rodjenja 44 | 45 | 46 | 47 | 48 | 49 | Molim unesite mesto stanovanja. 50 | 51 | 52 | 53 | 54 | 55 | Molim unesite drzavu stanovanja. 56 | 57 | 58 | 59 |
60 |
-------------------------------------------------------------------------------- /frontend/src/app/cars/car-info/car-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup } from '@angular/forms'; 3 | import { ActivatedRoute, ParamMap } from '@angular/router'; 4 | import { Subscription } from 'rxjs'; 5 | import { AdminService } from 'src/app/admin/admin.service'; 6 | import { AuthService } from 'src/app/auth/auth.service'; 7 | import { CarsService } from '../cars.service'; 8 | 9 | @Component({ 10 | selector: 'app-car-info', 11 | templateUrl: './car-info.component.html', 12 | styleUrls: ['./car-info.component.css'] 13 | }) 14 | export class CarInfoComponent implements OnInit, OnDestroy { 15 | path!:string; 16 | isLoading: boolean = false; 17 | form!: FormGroup; 18 | ownerName!: string; 19 | ownerId!: number; 20 | userIsAuthenticated: boolean = false; 21 | userIsAdmin: boolean = false; 22 | private carId!: number; 23 | private authStatusSub!: Subscription; 24 | private adminStatusSub!: Subscription; 25 | 26 | constructor(public carsService: CarsService, public route: ActivatedRoute, private authService: AuthService, private adminService: AdminService) { } 27 | 28 | ngOnInit(): void { 29 | this.userIsAuthenticated = this.authService.getIsAuth(); 30 | this.authStatusSub = this.authService.getAuthStatusListener() 31 | .subscribe(authStatus => { 32 | this.userIsAuthenticated = authStatus; 33 | this.isLoading = false; 34 | }); 35 | this.userIsAdmin = this.authService.getIsAdmin(); 36 | this.adminStatusSub = this.authService.getAdminStatusListener() 37 | .subscribe(isAdmin => { 38 | this.userIsAdmin = isAdmin; 39 | }); 40 | this.isLoading = false; 41 | this.form = new FormGroup({ 42 | title: new FormControl(null, []), 43 | brand: new FormControl(null, []), 44 | model: new FormControl(null, []), 45 | mileage: new FormControl(null, []), 46 | registration: new FormControl(null, []), 47 | price: new FormControl(null,[]), 48 | fuel: new FormControl(null, []), 49 | color: new FormControl(null, []), 50 | phone: new FormControl(null, []), 51 | content: new FormControl(null, []) 52 | }); 53 | this.route.paramMap.subscribe((paramMap: ParamMap) => { 54 | this.carId = Number(paramMap.get('carId')); 55 | this.isLoading = true; 56 | this.carsService.getCar(this.carId) 57 | .subscribe(carData => { 58 | this.path = carData.path; 59 | this.isLoading = false; 60 | this.ownerName = carData.owner.firstName + " " + carData.owner.lastName; 61 | this.ownerId = carData.owner.id; 62 | this.form.setValue({ 63 | title: carData.title, 64 | brand: carData.brand.name, 65 | model: carData.model.name, 66 | mileage: carData.mileage, 67 | registration: carData.registration, 68 | price: carData.price, 69 | fuel: carData.fuel.name, 70 | color: carData.color.name, 71 | phone: carData.phone, 72 | content: carData.content 73 | }); 74 | }); 75 | }); 76 | } 77 | 78 | onAdminDelete() { 79 | if (this.carId) { 80 | this.isLoading = true; 81 | this.adminService.adminDeleteCar(this.carId); 82 | } 83 | } 84 | 85 | ngOnDestroy(): void { 86 | this.authStatusSub.unsubscribe(); 87 | this.adminStatusSub.unsubscribe(); 88 | } 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/filters/CustomAuthorizationFilter.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.filters; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import javax.servlet.FilterChain; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 16 | import org.springframework.security.core.context.SecurityContextHolder; 17 | import org.springframework.web.filter.OncePerRequestFilter; 18 | 19 | import com.auth0.jwt.JWT; 20 | import com.auth0.jwt.algorithms.Algorithm; 21 | import com.auth0.jwt.interfaces.DecodedJWT; 22 | import com.auth0.jwt.interfaces.JWTVerifier; 23 | import com.fasterxml.jackson.databind.ObjectMapper; 24 | 25 | import lombok.extern.slf4j.Slf4j; 26 | 27 | @Slf4j 28 | public class CustomAuthorizationFilter extends OncePerRequestFilter { 29 | 30 | @Override 31 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 32 | throws ServletException, IOException { 33 | if (request.getServletPath().equals("/api/users/login") || request.getServletPath().equals("/api/users/signup") 34 | || request.getServletPath().contains("/api/posts/allPosts")) { 35 | log.info("Ovde se preskace za neke rute {}", request.getRequestURI()); 36 | filterChain.doFilter(request, response); 37 | } else { 38 | String authorizationHeader = request.getHeader(org.springframework.http.HttpHeaders.AUTHORIZATION); 39 | if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { 40 | log.info("Header nije null: {}, {}", authorizationHeader, request.getRequestURI()); 41 | try { 42 | String token = authorizationHeader.substring("Bearer ".length()); 43 | Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); 44 | JWTVerifier verifier = JWT.require(algorithm).build(); 45 | DecodedJWT decodedJWT = verifier.verify(token); 46 | String username = decodedJWT.getSubject(); 47 | String[] roles = decodedJWT.getClaim("roles").asArray(String.class); 48 | Collection authorities = new ArrayList<>(); 49 | java.util.Arrays.stream(roles).forEach(role -> { 50 | authorities.add(new SimpleGrantedAuthority(role)); 51 | }); 52 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities); 53 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 54 | filterChain.doFilter(request, response); 55 | } catch (Exception exception) { 56 | log.error("Error logging in: {}", exception.getMessage()); 57 | response.setHeader("error", exception.getMessage()); 58 | response.setStatus(org.springframework.http.HttpStatus.FORBIDDEN.value()); 59 | Map error = new HashMap<>(); 60 | error.put("error_message", exception.getMessage()); 61 | response.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE); 62 | new ObjectMapper().writeValue(response.getOutputStream(), error); 63 | } 64 | } else { 65 | log.info("Ako je header null {}", request.getRequestURI()); 66 | filterChain.doFilter(request, response); 67 | } 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/filters/CustomAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.filters; 2 | 3 | import java.io.IOException; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.stream.Collectors; 8 | 9 | import javax.servlet.FilterChain; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | import org.springframework.security.authentication.AuthenticationManager; 15 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 16 | import org.springframework.security.core.Authentication; 17 | import org.springframework.security.core.AuthenticationException; 18 | import org.springframework.security.core.GrantedAuthority; 19 | import org.springframework.security.core.userdetails.User; 20 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 21 | 22 | import com.app.backend.models.LoginData; 23 | import com.auth0.jwt.JWT; 24 | import com.auth0.jwt.algorithms.Algorithm; 25 | import com.fasterxml.jackson.databind.ObjectMapper; 26 | 27 | import lombok.extern.slf4j.Slf4j; 28 | 29 | @Slf4j 30 | public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { 31 | private AuthenticationManager authenticationManager; 32 | 33 | public CustomAuthenticationFilter(AuthenticationManager authenticationManager) { 34 | this.authenticationManager = authenticationManager; 35 | } 36 | 37 | @Override 38 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 39 | throws AuthenticationException { 40 | LoginData authClass; 41 | try { 42 | authClass = new ObjectMapper().readValue(request.getInputStream(), LoginData.class); 43 | String username = authClass.getUsername(); 44 | String password = authClass.getPassword(); 45 | log.info("Username is: {}", username); 46 | log.info("Password is: {}", password); 47 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); 48 | return authenticationManager.authenticate(authenticationToken); 49 | } catch (IOException e) { 50 | throw new RuntimeException(e); 51 | } 52 | } 53 | 54 | @Override 55 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, 56 | Authentication authentication) throws IOException, ServletException { 57 | User user = (User)authentication.getPrincipal(); 58 | Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); 59 | String access_token = JWT.create() 60 | .withSubject(user.getUsername()) 61 | .withExpiresAt(new Date(System.currentTimeMillis() + 10 * 60 * 1000)) 62 | .withIssuer(request.getRequestURL().toString()) 63 | .withClaim("roles", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())) 64 | .sign(algorithm); 65 | Map tokens = new HashMap<>(); 66 | tokens.put("access_token", access_token); 67 | tokens.put("expiresIn", "600"); 68 | response.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE); 69 | new ObjectMapper().writeValue(response.getOutputStream(), tokens); 70 | } 71 | 72 | @Override 73 | protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, 74 | AuthenticationException failed) throws IOException, ServletException { 75 | log.info("Unsuccessful Authentication"); 76 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Pogresni kredencijali za prijavu"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /frontend/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { AddBrandComponent } from './admin/add-brand/add-brand.component'; 4 | import { AddColorComponent } from './admin/add-color/add-color.component'; 5 | import { AddFuelTypeComponent } from './admin/add-fuel-type/add-fuel-type.component'; 6 | import { AddModelComponent } from './admin/add-model/add-model.component'; 7 | import { AdminControllerComponent } from './admin/admin-controller/admin-controller.component'; 8 | import { AdminGuard } from './admin/admin.guard'; 9 | import { BlockGuard } from './admin/block.guard'; 10 | import { DeleteBrandComponent } from './admin/delete-brand/delete-brand.component'; 11 | import { DeleteColorComponent } from './admin/delete-color/delete-color.component'; 12 | import { DeleteFuelTypeComponent } from './admin/delete-fuel-type/delete-fuel-type.component'; 13 | import { DeleteModelComponent } from './admin/delete-model/delete-model.component'; 14 | import { AuthGuard } from './auth/auth.guard'; 15 | import { LoginComponent } from './auth/login/login.component'; 16 | import { PasswordChangeComponent } from './auth/password-change/password-change.component'; 17 | import { RegisterComponent } from './auth/register/register.component'; 18 | import { UserChangeComponent } from './auth/user-change/user-change.component'; 19 | import { UserInfoComponent } from './auth/user-info/user-info.component'; 20 | import { CarAddComponent } from './cars/car-add/car-add.component'; 21 | import { CarEditComponent } from './cars/car-edit/car-edit.component'; 22 | import { CarInfoComponent } from './cars/car-info/car-info.component'; 23 | import { CarListComponent } from './cars/car-list/car-list.component'; 24 | import { MyCarListComponent } from './cars/my-car-list/my-car-list.component'; 25 | 26 | const routes: Routes = [ 27 | { path: '', component: CarListComponent }, 28 | { path: 'add', component: CarAddComponent, canActivate: [AuthGuard, BlockGuard]}, 29 | { path: 'carInfo/:carId', component: CarInfoComponent, canActivate: [AuthGuard] }, 30 | { path: 'carEdit/:carId', component: CarEditComponent, canActivate: [AuthGuard, BlockGuard] }, 31 | { path: 'myCarList', component: MyCarListComponent, canActivate: [AuthGuard, BlockGuard] }, 32 | { path: 'admin', component: AdminControllerComponent, canActivate: [AuthGuard, AdminGuard] }, 33 | { path: 'addFuelType', component: AddFuelTypeComponent, canActivate: [AuthGuard, AdminGuard] }, 34 | { path: 'deleteFuelType', component: DeleteFuelTypeComponent, canActivate: [AuthGuard, AdminGuard] }, 35 | { path: 'addColor', component: AddColorComponent, canActivate: [AuthGuard, AdminGuard] }, 36 | { path: 'deleteColor', component: DeleteColorComponent, canActivate: [AuthGuard, AdminGuard] }, 37 | { path: 'addBrand', component: AddBrandComponent, canActivate: [AuthGuard, AdminGuard] }, 38 | { path: 'deleteBrand', component: DeleteBrandComponent, canActivate: [AuthGuard, AdminGuard] }, 39 | { path: 'addModel', component: AddModelComponent, canActivate: [AuthGuard, AdminGuard] }, 40 | { path: 'deleteModel', component: DeleteModelComponent, canActivate: [AuthGuard, AdminGuard] }, 41 | { path: 'userInfo/:userId', component: UserInfoComponent, canActivate: [AuthGuard] }, 42 | { path: 'passwordChange/:userId', component: PasswordChangeComponent, canActivate: [AuthGuard] }, 43 | { path: 'userChange/:userId', component: UserChangeComponent, canActivate: [AuthGuard] }, 44 | { path: 'login', component: LoginComponent}, 45 | { path: 'register', component: RegisterComponent } 46 | ]; 47 | 48 | @NgModule({ 49 | imports: [RouterModule.forRoot(routes)], 50 | exports: [RouterModule], 51 | providers: [AuthGuard, AdminGuard, BlockGuard] 52 | }) 53 | export class AppRoutingModule { } 54 | -------------------------------------------------------------------------------- /frontend/src/app/admin/admin-controller/admin-controller.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Administratorske kontrole 4 | 5 | 6 |
    7 |
  • 8 | 9 |
  • 10 |
  • 11 | 12 |
  • 13 |
14 |
15 |
16 | 17 |
    18 |
  • 19 | 20 |
  • 21 |
  • 22 | 23 |
  • 24 |
25 |
26 |
27 | 28 |
    29 |
  • 30 | 31 |
  • 32 |
  • 33 | 34 |
  • 35 |
  • 36 | 37 |
  • 38 |
  • 39 | 40 |
  • 41 |
42 |
43 |
44 |
45 | 46 | Korisnici 47 | 48 | 49 | 50 | Ime 51 | {{element.firstName}} 52 | 53 | 54 | Prezime 55 | {{element.lastName}} 56 | 57 | 58 | Informacije 59 | 60 | 63 | 64 | 65 | 66 | Dozvole za korisnika 67 | 68 | 72 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.crypto.password.PasswordEncoder; 15 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 16 | 17 | import com.app.backend.filters.CustomAuthenticationFilter; 18 | import com.app.backend.filters.CustomAuthorizationFilter; 19 | 20 | @Configuration 21 | @EnableWebSecurity 22 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 23 | @Autowired 24 | private UserDetailsService userDetailsService; 25 | @Autowired 26 | private PasswordEncoder passwordEncoder; 27 | 28 | @Override 29 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 30 | auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); 31 | } 32 | 33 | @Override 34 | protected void configure(HttpSecurity http) throws Exception { 35 | CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean()); 36 | customAuthenticationFilter.setFilterProcessesUrl("/api/users/login"); 37 | http.csrf().disable(); 38 | http.cors(); 39 | http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 40 | http.authorizeRequests().antMatchers("/api/users/login/**", "/api/users/signup/**", "/error").permitAll(); 41 | http.authorizeRequests().antMatchers(HttpMethod.GET, "/api/users/logout/**").permitAll(); 42 | http.authorizeRequests().antMatchers(HttpMethod.GET, "/api/users/getUserId/**", "/api/users/hasAdminPrivilege", 43 | "/api/users/getUserById/**").hasAnyAuthority("ROLE_USER"); 44 | http.authorizeRequests().antMatchers(HttpMethod.POST, "/api/users/changePassword/**").hasAnyAuthority("ROLE_USER"); 45 | http.authorizeRequests().antMatchers(HttpMethod.PUT, "/api/users/updateUser/**").hasAnyAuthority("ROLE_USER"); 46 | http.authorizeRequests().antMatchers(HttpMethod.GET, "/api/users/allUsers/**", "/api/role/save/**", 47 | "/api/role/addtouser/**").hasAnyAuthority("ROLE_ADMIN"); 48 | http.authorizeRequests().antMatchers(HttpMethod.POST, "/api/cars/addCar/**").hasAnyAuthority("ROLE_USER"); 49 | http.authorizeRequests().antMatchers(HttpMethod.GET, "/api/cars/getCar/**", "/api/cars/getCars/**", 50 | "/api/cars/getMyCars/**", "/images/**").permitAll(); 51 | http.authorizeRequests().antMatchers(HttpMethod.PUT, "/api/cars/updateCarImage/**", "/api/cars/updateCarNoImage/**").hasAnyAuthority("ROLE_USER"); 52 | http.authorizeRequests().antMatchers(HttpMethod.DELETE, "/api/cars/deleteCar/**").hasAnyAuthority("ROLE_USER"); 53 | http.authorizeRequests().antMatchers(HttpMethod.GET, "/api/admin/getUsers/**", "/api/admin/blockUser/**", "/api/admin/unblockUser/**").hasAnyAuthority("ROLE_ADMIN"); 54 | http.authorizeRequests().antMatchers(HttpMethod.GET, "/api/admin/getBrands/**", "/api/admin/getModels/**", 55 | "/api/admin/getFuelTypes/**", "/api/admin/getColors/**").hasAnyAuthority("ROLE_USER"); 56 | http.authorizeRequests().antMatchers(HttpMethod.POST, "/api/admin/addBrand/**", "/api/admin/addModel/**", 57 | "/api/admin/addFuelType/**", "/api/admin/addColor/**").hasAnyAuthority("ROLE_ADMIN"); 58 | http.authorizeRequests().antMatchers(HttpMethod.DELETE, "/api/admin/deleteBrand/**", "/api/admin/deleteModel/**", 59 | "/api/admin/deleteFuelType/**", "/api/admin/deleteColor/**").hasAnyAuthority("ROLE_ADMIN"); 60 | http.authorizeRequests().anyRequest().authenticated(); 61 | http.addFilter(customAuthenticationFilter); 62 | http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class); 63 | 64 | } 65 | 66 | @Bean 67 | @Override 68 | public AuthenticationManager authenticationManagerBean() throws Exception { 69 | return super.authenticationManagerBean(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { AppRoutingModule } from './app-routing.module'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 6 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 7 | 8 | import { AppComponent } from './app.component'; 9 | import { HeaderComponent } from './header/header.component'; 10 | import { RegisterComponent } from './auth/register/register.component'; 11 | import { LoginComponent } from './auth/login/login.component'; 12 | import { ErrorComponent } from './error/error.component'; 13 | import { CarAddComponent } from './cars/car-add/car-add.component'; 14 | import { CarListComponent } from './cars/car-list/car-list.component'; 15 | import { AuthInterceptor } from './auth/auth-interceptor'; 16 | import { ErrorInterceptor } from './error-interceptor'; 17 | 18 | import { MatInputModule } from '@angular/material/input'; 19 | import { MatCardModule } from '@angular/material/card'; 20 | import { MatButtonModule } from '@angular/material/button'; 21 | import { MatToolbarModule } from '@angular/material/toolbar'; 22 | import { MatExpansionModule } from '@angular/material/expansion'; 23 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 24 | import { MatPaginatorModule } from '@angular/material/paginator'; 25 | import { MatDialogModule } from '@angular/material/dialog'; 26 | import { MatDatepickerModule } from '@angular/material/datepicker'; 27 | import { MatNativeDateModule } from "@angular/material/core"; 28 | import { MatSelectModule } from '@angular/material/select'; 29 | import { MatTableModule } from '@angular/material/table'; 30 | import { MatIconModule } from '@angular/material/icon'; 31 | import { CarInfoComponent } from './cars/car-info/car-info.component'; 32 | import { MyCarListComponent } from './cars/my-car-list/my-car-list.component'; 33 | import { CarEditComponent } from './cars/car-edit/car-edit.component'; 34 | import { AdminControllerComponent } from './admin/admin-controller/admin-controller.component'; 35 | import { AddFuelTypeComponent } from './admin/add-fuel-type/add-fuel-type.component'; 36 | import { DeleteFuelTypeComponent } from './admin/delete-fuel-type/delete-fuel-type.component'; 37 | import { AddColorComponent } from './admin/add-color/add-color.component'; 38 | import { DeleteColorComponent } from './admin/delete-color/delete-color.component'; 39 | import { AddBrandComponent } from './admin/add-brand/add-brand.component'; 40 | import { DeleteBrandComponent } from './admin/delete-brand/delete-brand.component'; 41 | import { AddModelComponent } from './admin/add-model/add-model.component'; 42 | import { DeleteModelComponent } from './admin/delete-model/delete-model.component'; 43 | import { UserInfoComponent } from './auth/user-info/user-info.component'; 44 | import { PasswordChangeComponent } from './auth/password-change/password-change.component'; 45 | import { UserChangeComponent } from './auth/user-change/user-change.component'; 46 | 47 | 48 | @NgModule({ 49 | declarations: [ 50 | AppComponent, 51 | HeaderComponent, 52 | RegisterComponent, 53 | LoginComponent, 54 | ErrorComponent, 55 | CarAddComponent, 56 | CarListComponent, 57 | CarInfoComponent, 58 | MyCarListComponent, 59 | CarEditComponent, 60 | AdminControllerComponent, 61 | AddFuelTypeComponent, 62 | DeleteFuelTypeComponent, 63 | AddColorComponent, 64 | DeleteColorComponent, 65 | AddBrandComponent, 66 | DeleteBrandComponent, 67 | AddModelComponent, 68 | DeleteModelComponent, 69 | UserInfoComponent, 70 | PasswordChangeComponent, 71 | UserChangeComponent 72 | ], 73 | imports: [ 74 | BrowserModule, 75 | AppRoutingModule, 76 | BrowserAnimationsModule, 77 | ReactiveFormsModule, 78 | FormsModule, 79 | MatInputModule, 80 | MatCardModule, 81 | MatButtonModule, 82 | MatToolbarModule, 83 | MatExpansionModule, 84 | MatProgressSpinnerModule, 85 | MatPaginatorModule, 86 | MatDatepickerModule, 87 | MatNativeDateModule, 88 | MatDialogModule, 89 | MatSelectModule, 90 | MatTableModule, 91 | MatIconModule, 92 | HttpClientModule 93 | ], 94 | providers: [ 95 | {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}, 96 | {provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true} 97 | ], 98 | bootstrap: [AppComponent], 99 | entryComponents: [ErrorComponent] 100 | }) 101 | export class AppModule { } 102 | -------------------------------------------------------------------------------- /frontend/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "frontend": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:application": { 10 | "strict": true 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/frontend", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets" 28 | ], 29 | "styles": [ 30 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 31 | "src/styles.css" 32 | ], 33 | "scripts": [] 34 | }, 35 | "configurations": { 36 | "production": { 37 | "budgets": [{ 38 | "type": "initial", 39 | "maximumWarning": "500kb", 40 | "maximumError": "1mb" 41 | }, 42 | { 43 | "type": "anyComponentStyle", 44 | "maximumWarning": "2kb", 45 | "maximumError": "4kb" 46 | } 47 | ], 48 | "fileReplacements": [{ 49 | "replace": "src/environments/environment.ts", 50 | "with": "src/environments/environment.prod.ts" 51 | }], 52 | "outputHashing": "all" 53 | }, 54 | "development": { 55 | "buildOptimizer": false, 56 | "optimization": false, 57 | "vendorChunk": true, 58 | "extractLicenses": false, 59 | "sourceMap": true, 60 | "namedChunks": true 61 | } 62 | }, 63 | "defaultConfiguration": "production" 64 | }, 65 | "serve": { 66 | "builder": "@angular-devkit/build-angular:dev-server", 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "frontend:build:production" 70 | }, 71 | "development": { 72 | "browserTarget": "frontend:build:development" 73 | } 74 | }, 75 | "defaultConfiguration": "development" 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "frontend:build" 81 | } 82 | }, 83 | "test": { 84 | "builder": "@angular-devkit/build-angular:karma", 85 | "options": { 86 | "main": "src/test.ts", 87 | "polyfills": "src/polyfills.ts", 88 | "tsConfig": "tsconfig.spec.json", 89 | "karmaConfig": "karma.conf.js", 90 | "assets": [ 91 | "src/favicon.ico", 92 | "src/assets" 93 | ], 94 | "styles": [ 95 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 96 | "src/styles.css" 97 | ], 98 | "scripts": [] 99 | } 100 | } 101 | } 102 | } 103 | }, 104 | "defaultProject": "frontend" 105 | } -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/controllers/CarController.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.controllers; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.DeleteMapping; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.PutMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestHeader; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestParam; 17 | import org.springframework.web.bind.annotation.RestController; 18 | import org.springframework.web.multipart.MultipartFile; 19 | 20 | import com.app.backend.models.Car; 21 | import com.app.backend.models.FrontCar; 22 | import com.app.backend.models.User; 23 | import com.app.backend.services.CarService; 24 | import com.app.backend.services.UserService; 25 | import com.app.backend.util.CarResponse; 26 | 27 | import lombok.extern.slf4j.Slf4j; 28 | 29 | 30 | @RestController 31 | @RequestMapping("/api/cars") 32 | @Slf4j 33 | public class CarController { 34 | @Autowired 35 | private UserService userService; 36 | @Autowired 37 | private CarService carService; 38 | 39 | @PostMapping("/addCar") 40 | public ResponseEntity addCar(@RequestParam(name = "image") MultipartFile image, 41 | @RequestParam(name = "car") String carData, @RequestHeader (name="Authorization") String authorizationHeader) { 42 | try { 43 | log.info(carData); 44 | User user = userService.getUserFromAuthorizationHeader(authorizationHeader); 45 | return ResponseEntity.ok().body(carService.addCar(image, carData, user)); 46 | } catch (IOException ex) { 47 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new Error("Greska na serveru!")); 48 | } 49 | } 50 | 51 | @GetMapping("/getCar/{id}") 52 | public ResponseEntity getCar(@PathVariable Long id) { 53 | return ResponseEntity.ok().body(carService.getCar(id)); 54 | } 55 | 56 | @GetMapping("/getCars") 57 | public ResponseEntity getCars(@RequestParam(value = "pagesize", required = false) Integer pagesize, 58 | @RequestParam(value = "page", required = false) Integer page) { 59 | return ResponseEntity.ok().body(carService.getCars(page, pagesize)); 60 | } 61 | 62 | @GetMapping("/getMyCars/{id}") 63 | public ResponseEntity getMyCars(@PathVariable Long id, @RequestParam(value = "pagesize", required = false) Integer pagesize, 64 | @RequestParam(value = "page", required = false) Integer page) { 65 | User user = userService.getUserById(id); 66 | return ResponseEntity.ok().body(carService.getMyCars(user, page, pagesize)); 67 | } 68 | 69 | @PutMapping("/updateCarImage/{id}") 70 | public ResponseEntity updatePostImage(@PathVariable Long id, @RequestParam(name = "image") MultipartFile image, 71 | @RequestParam(name = "car") String carData, @RequestHeader (name="Authorization") String authorizationHeader) { 72 | User user = userService.getUserFromAuthorizationHeader(authorizationHeader); 73 | try { 74 | Car savedCar = carService.updateCarImage(id, image, carData, user); 75 | if (savedCar != null) { 76 | return ResponseEntity.ok().body(savedCar); 77 | } else { 78 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new Error("Niste autorizovani!")); 79 | } 80 | } catch (IOException ex) { 81 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new Error("Greska na serveru!")); 82 | } 83 | } 84 | 85 | @PutMapping("/updateCarNoImage/{id}") 86 | public ResponseEntity updatePostNoImage(@PathVariable Long id, @RequestBody FrontCar frontCar, @RequestHeader (name="Authorization") String authorizationHeader) { 87 | User user = userService.getUserFromAuthorizationHeader(authorizationHeader); 88 | Car savedCar = carService.updateCarNoImage(id, frontCar, user); 89 | if (savedCar != null) { 90 | return ResponseEntity.ok().body(savedCar); 91 | } else { 92 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new Error("Niste autorizovani!")); 93 | } 94 | } 95 | 96 | @DeleteMapping("/deleteCar/{id}") 97 | public ResponseEntity deleteCar(@PathVariable Long id, @RequestHeader (name="Authorization") String authorizationHeader) { 98 | User user = userService.getUserFromAuthorizationHeader(authorizationHeader); 99 | Long deletedCarId = carService.deleteCar(id, user); 100 | if (deletedCarId != null) { 101 | return ResponseEntity.ok().body(deletedCarId); 102 | } else { 103 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new Error("Niste autorizovani!")); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-add/car-add.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Novi oglas 4 | 5 | 6 |
7 | 8 | 9 | 10 | Molim unesite naslov oglasa. 11 | 12 | 13 | 14 | 15 | 16 | {{brand}} 17 | 18 | 19 | 20 | Molim unesite marku automobila. 21 | 22 | 23 | 24 | 25 | 26 | {{model}} 27 | 28 | 29 | 30 | Molim unesite model automobila. 31 | 32 | 33 |
34 | 35 | 36 |
37 |
38 | 39 |
40 | {{ imageErrorMessage }} 41 | 42 | 43 | 44 | Molim unesite kilometrazu automobila. 45 | 46 | 47 | Unesite kilometrazu od 0 do 999999! 48 | 49 | 50 | 51 | 52 | 53 | {{year}} 54 | 55 | 56 | 57 | Molim unesite godiste automobila. 58 | 59 | 60 | 61 | 62 | 63 | Molim unesite model automobila. 64 | 65 | 66 | Unesite cenu od 0 do 999999! 67 | 68 | 69 | 70 | 71 | 72 | {{fuelType}} 73 | 74 | 75 | 76 | Molim unesite tip goriva automobila. 77 | 78 | 79 | 80 | 81 | 82 | {{color}} 83 | 84 | 85 | 86 | Molim unesite boju automobila. 87 | 88 | 89 | 90 | 91 | 92 | Molim unesite broj telefona. 93 | 94 | 95 | Unesite broj telefona u odgovarajucem formatu! 96 | 97 | 98 | 99 | 100 | 101 | Molim unesite opis oglasa. 102 | 103 | 104 | 105 |
106 |
-------------------------------------------------------------------------------- /frontend/src/app/cars/car-edit/car-edit.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Azuriraj podatke o oglasu 8 | 9 | 10 |
11 | 12 | 13 | 14 | Molim unesite naslov oglasa. 15 | 16 | 17 | 18 | 19 | 20 | {{brand}} 21 | 22 | 23 | 24 | Molim unesite marku automobila. 25 | 26 | 27 | 28 | 29 | 30 | {{model}} 31 | 32 | 33 | 34 | Molim unesite model automobila. 35 | 36 | 37 |
38 | 39 | 40 |
41 |
42 | 43 |
44 | 45 | 46 | 47 | Molim unesite kilometrazu automobila. 48 | 49 | 50 | Unesite kilometrazu od 0 do 999999! 51 | 52 | 53 | 54 | 55 | 56 | {{year}} 57 | 58 | 59 | 60 | Molim unesite godiste automobila. 61 | 62 | 63 | 64 | 65 | 66 | Molim unesite model automobila. 67 | 68 | 69 | Unesite cenu od 0 do 999999! 70 | 71 | 72 | 73 | 74 | 75 | {{fuelType}} 76 | 77 | 78 | 79 | Molim unesite tip goriva automobila. 80 | 81 | 82 | 83 | 84 | 85 | {{color}} 86 | 87 | 88 | 89 | Molim unesite boju automobila. 90 | 91 | 92 | 93 | 94 | 95 | Molim unesite broj telefona. 96 | 97 | 98 | Unesite broj telefona u odgovarajucem formatu! 99 | 100 | 101 | 102 | 103 | 104 | Molim unesite opis oglasa. 105 | 106 | 107 | 108 |
109 |
-------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/controllers/UserController.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.controllers; 2 | 3 | import java.net.URI; 4 | import java.time.ZoneId; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.PutMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestHeader; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RestController; 20 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder; 21 | 22 | import com.app.backend.models.FrontUser; 23 | import com.app.backend.models.Role; 24 | import com.app.backend.models.SignupData; 25 | import com.app.backend.models.User; 26 | import com.app.backend.services.UserService; 27 | 28 | import lombok.Data; 29 | 30 | @RestController 31 | @RequestMapping("/api") 32 | public class UserController { 33 | @Autowired 34 | private UserService userService; 35 | 36 | @GetMapping("/users/getUserId") 37 | public ResponseEntity getUserId(@RequestHeader (name="Authorization") String authorizationHeader) { 38 | User user = userService.getUserFromAuthorizationHeader(authorizationHeader); 39 | return ResponseEntity.ok().body(user.getId()); 40 | } 41 | 42 | @GetMapping("/users/hasAdminPrivilege") 43 | public ResponseEntity hasAdminPrivilege(@RequestHeader (name="Authorization") String authorizationHeader) { 44 | return ResponseEntity.ok().body(userService.hasAdminPrivilege(authorizationHeader)); 45 | } 46 | 47 | @GetMapping("/users/isBlocked/{userId}") 48 | public ResponseEntity isBlocked(@PathVariable Long userId) { 49 | return ResponseEntity.ok().body(userService.isBlocked(userId)); 50 | } 51 | 52 | @GetMapping("/users/getUserById/{userId}") 53 | public ResponseEntity getUserById(@PathVariable Long userId) { 54 | User user = userService.getUserById(userId); 55 | FrontUser frontUser = new FrontUser(user.getId(), user.getFirstName(), user.getLastName(), 56 | user.getBrithdate().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli(), 57 | user.getCity(), user.getCountry()); 58 | return ResponseEntity.ok().body(frontUser); 59 | } 60 | 61 | @PostMapping("/users/signup") 62 | public ResponseEntity signupUser(@RequestBody SignupData signupData) { 63 | URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/users/signup").toUriString()); 64 | User savedUser = userService.signupUser(signupData); 65 | if (savedUser != null) { 66 | return ResponseEntity.created(uri).body(savedUser); 67 | } else { 68 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new Error("E-mail adresa je zauzeta!")); 69 | } 70 | } 71 | 72 | @PostMapping("/users/changePassword") 73 | public ResponseEntity changePassword(@RequestBody ChangePasswordData changePasswordData) { 74 | User changedUser = userService.changePassword(changePasswordData.getUserId(), changePasswordData.getOldPassword(), changePasswordData.getNewPassword()); 75 | if (changedUser != null) { 76 | return ResponseEntity.ok().body(changedUser); 77 | } else { 78 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new Error("Nije dobra stara lozinka!")); 79 | } 80 | } 81 | 82 | @PutMapping("/users/updateUser/{userId}") 83 | public ResponseEntity updateUser(@PathVariable Long userId, @RequestBody ChangeUserData changeUserData) { 84 | User user = userService.getUserById(userId); 85 | User updatedUser = userService.updateUser(user, changeUserData.getFirstName(), changeUserData.getLastName(), 86 | changeUserData.getTimestamp(), changeUserData.getCity(), changeUserData.getCountry()); 87 | return ResponseEntity.ok().body(updatedUser); 88 | } 89 | 90 | @GetMapping("/users/logout") 91 | public ResponseEntity logout(HttpServletRequest request, HttpServletResponse response) { 92 | return ResponseEntity.ok().body(userService.logoutUser(request, response)); 93 | } 94 | 95 | @PostMapping("/role/save") 96 | public ResponseEntity saveRole(@RequestBody Role role) { 97 | URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/role/save").toUriString()); 98 | return ResponseEntity.created(uri).body(userService.saveRole(role)); 99 | } 100 | 101 | @PostMapping("/role/addtouser") 102 | public ResponseEntity addRoleToUser(@RequestBody RoleToUserForm form) { 103 | userService.addRoleToUser(form.getUsername(), form.getRoleName()); 104 | return ResponseEntity.ok().build(); 105 | } 106 | } 107 | 108 | @Data 109 | class RoleToUserForm { 110 | private String username; 111 | private String roleName; 112 | } 113 | 114 | @Data 115 | class ChangePasswordData { 116 | private Long userId; 117 | private String oldPassword; 118 | private String newPassword; 119 | } 120 | 121 | @Data 122 | class ChangeUserData { 123 | private String firstName; 124 | private String lastName; 125 | private Long timestamp; 126 | private String city; 127 | private String country; 128 | } 129 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-add/car-add.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { ActivatedRoute, ParamMap } from '@angular/router'; 4 | import { Subscription } from 'rxjs'; 5 | import { AdminService } from 'src/app/admin/admin.service'; 6 | import { AuthService } from 'src/app/auth/auth.service'; 7 | import { CarsService } from '../cars.service'; 8 | import { mimeType } from "./mime-type.validator"; 9 | 10 | 11 | @Component({ 12 | selector: 'app-car-add', 13 | templateUrl: './car-add.component.html', 14 | styleUrls: ['./car-add.component.css'] 15 | }) 16 | export class CarAddComponent implements OnInit, OnDestroy { 17 | form!: FormGroup; 18 | imagePreview!: string; 19 | isLoading: boolean = false; 20 | brands: string[] = []; 21 | models: string[] = []; 22 | fuelTypes: string[] = []; 23 | colors: string[] = []; 24 | years: number[] = []; 25 | imageErrorMessage: string = ""; 26 | private authStatusSub!: Subscription; 27 | private brandsSub!: Subscription; 28 | private modelsSub!: Subscription; 29 | private fuelTypesSub!: Subscription; 30 | private colorsSub!: Subscription; 31 | 32 | constructor(public carsService: CarsService, public route: ActivatedRoute, private authService: AuthService, private adminService: AdminService) { } 33 | 34 | ngOnInit(): void { 35 | let date: Date = new Date(); 36 | let finalYear = date.getFullYear(); 37 | for(let i = finalYear; i >= 1980; i--) { 38 | this.years.push(i); 39 | } 40 | 41 | this.authStatusSub = this.authService.getAuthStatusListener().subscribe( 42 | authStatus => { 43 | this.isLoading = false; 44 | } 45 | ); 46 | this.adminService.getBrands(); 47 | this.brandsSub = this.adminService.getBrandsUpdateListener() 48 | .subscribe((brandsData: {brands: string[]}) => { 49 | this.brands = brandsData.brands; 50 | }); 51 | this.modelsSub = this.adminService.getModelsUpdateListener() 52 | .subscribe((modelsData: {models: string[]}) => { 53 | this.models = modelsData.models; 54 | }); 55 | this.adminService.getFuelTypes(); 56 | this.fuelTypesSub = this.adminService.getFuelTypesUpdateListener() 57 | .subscribe((fuelTypesData: {fuelTypes: string[]}) => { 58 | this.fuelTypes = fuelTypesData.fuelTypes; 59 | }); 60 | this.adminService.getColors(); 61 | this.colorsSub = this.adminService.getColorsUpdateListener() 62 | .subscribe((colorsData: {colors: string[]}) => { 63 | this.colors = colorsData.colors; 64 | }); 65 | 66 | this.form = new FormGroup({ 67 | title: new FormControl(null, { 68 | validators: [Validators.required] 69 | }), 70 | brand: new FormControl(null, { 71 | validators: [Validators.required] 72 | }), 73 | model: new FormControl(null, { 74 | validators: [Validators.required] 75 | }), 76 | image: new FormControl(null, { 77 | validators: [Validators.required], 78 | asyncValidators: [mimeType] 79 | }), 80 | mileage: new FormControl(null, { 81 | validators: [ 82 | Validators.required, 83 | Validators.pattern("^\\d{1,6}$") 84 | ] 85 | }), 86 | registration: new FormControl(null, { 87 | validators: [Validators.required] 88 | }), 89 | price: new FormControl(null, { 90 | validators: [ 91 | Validators.required, 92 | Validators.pattern("^\\d{1,6}$") 93 | ] 94 | }), 95 | fuel: new FormControl(null, { 96 | validators: [Validators.required] 97 | }), 98 | color: new FormControl(null, { 99 | validators: [Validators.required] 100 | }), 101 | phone: new FormControl(null, { 102 | validators: [ 103 | Validators.required, 104 | Validators.pattern("^\\+381/?[0-9]{2}-?[0-9]{2}-?[0-9]{2}-?[0-9]{3}$") 105 | ] 106 | }), 107 | content: new FormControl(null, { 108 | validators: [Validators.required] 109 | }) 110 | }); 111 | } 112 | 113 | onImagePicked(event: Event) { 114 | const files = (event.target as HTMLInputElement).files; 115 | if (files !== null) { 116 | const file = files[0]; 117 | this.form.patchValue({ 'image': file }); 118 | this.form.get('image')?.updateValueAndValidity(); 119 | const reader = new FileReader(); 120 | reader.onload = () => { 121 | this.imagePreview = reader.result as string; 122 | }; 123 | reader.readAsDataURL(file); 124 | } 125 | } 126 | 127 | onSaveCar() { 128 | if (this.form.get('image')?.invalid) { 129 | this.imageErrorMessage = "Unesite sliku."; 130 | } 131 | if (this.form.invalid) { 132 | return; 133 | } 134 | this.isLoading = true; 135 | this.carsService.addCar( 136 | this.form.value.title, 137 | this.form.value.brand, 138 | this.form.value.model, 139 | this.form.value.mileage, 140 | this.form.value.registration, 141 | this.form.value.price, 142 | this.form.value.fuel, 143 | this.form.value.color, 144 | this.form.value.phone, 145 | this.form.value.content, 146 | this.form.value.image 147 | ); 148 | this.form.reset(); 149 | } 150 | 151 | onChange(brandName: string) { 152 | this.adminService.getModels(brandName); 153 | } 154 | 155 | ngOnDestroy(): void { 156 | this.authStatusSub.unsubscribe(); 157 | this.brandsSub.unsubscribe(); 158 | this.modelsSub.unsubscribe(); 159 | this.fuelTypesSub.unsubscribe(); 160 | this.colorsSub.unsubscribe(); 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/services/AdminServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.services; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import com.app.backend.models.Brand; 10 | import com.app.backend.models.Car; 11 | import com.app.backend.models.Color; 12 | import com.app.backend.models.Fuel; 13 | import com.app.backend.models.Model; 14 | import com.app.backend.models.User; 15 | import com.app.backend.repositories.BrandRepository; 16 | import com.app.backend.repositories.CarRepository; 17 | import com.app.backend.repositories.ColorRepository; 18 | import com.app.backend.repositories.FuelRepository; 19 | import com.app.backend.repositories.ModelRepository; 20 | import com.app.backend.repositories.UserRepository; 21 | 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | @Service 25 | @Slf4j 26 | public class AdminServiceImpl implements AdminService { 27 | @Autowired 28 | private UserRepository userRepository; 29 | @Autowired 30 | private CarRepository carRepository; 31 | @Autowired 32 | private BrandRepository brandRepository; 33 | @Autowired 34 | private ModelRepository modelRepository; 35 | @Autowired 36 | private FuelRepository fuelRepository; 37 | @Autowired 38 | private ColorRepository colorRepository; 39 | 40 | @Override 41 | public User blockUser(Long userId) { 42 | log.info("Block user"); 43 | User user = userRepository.getById(userId); 44 | user.setBanned(true); 45 | return userRepository.save(user); 46 | } 47 | 48 | @Override 49 | public User unblockUser(Long userId) { 50 | log.info("Unblock user"); 51 | User user = userRepository.getById(userId); 52 | user.setBanned(false); 53 | return userRepository.save(user); 54 | } 55 | 56 | @Override 57 | public List getUsers(User user) { 58 | log.info("Fetching all users"); 59 | List users = userRepository.findAll(); 60 | List returnUsers = new ArrayList<>(); 61 | users.forEach(currentUser -> { 62 | if (currentUser.getId() != user.getId()) { 63 | returnUsers.add(currentUser); 64 | } 65 | }); 66 | return returnUsers; 67 | } 68 | 69 | @Override 70 | public Long adminDeleteCar(Long carId) { 71 | carRepository.deleteById(carId); 72 | log.info("Admin delete car"); 73 | return carId; 74 | } 75 | 76 | @Override 77 | public List getBrands() { 78 | log.info("Get brands"); 79 | return brandRepository.findAll(); 80 | } 81 | 82 | @Override 83 | public Brand saveBrand(String brandName) { 84 | Brand brand = brandRepository.findByName(brandName); 85 | if (brand != null) return null; 86 | log.info("Saving new brand {} to the database", brandName); 87 | return brandRepository.save(new Brand(null, brandName)); 88 | } 89 | 90 | @Override 91 | public Long deleteBrand(String brandName) { 92 | Brand brand = brandRepository.findByName(brandName); 93 | // List cars = carRepository.findByBrand(brand); 94 | List models = modelRepository.findByBrand(brand); 95 | // if(cars.size() > 0 || models.size() > 0) return null; 96 | if (models.size() > 0) return null; 97 | Long id = brand.getId(); 98 | brandRepository.deleteById(id); 99 | log.info("Delete a brand"); 100 | return id; 101 | } 102 | 103 | @Override 104 | public List getModels(String brandName) { 105 | Brand brand = brandRepository.findByName(brandName); 106 | log.info("Get models for brand {}", brandName); 107 | return modelRepository.findByBrand(brand); 108 | } 109 | 110 | @Override 111 | public Model addModelToBrand(String brandName, String modelName) { 112 | Model modelCheck = modelRepository.findByName(modelName); 113 | if (modelCheck != null) return null; 114 | modelRepository.save(new Model(null, modelName, null)); 115 | Brand brand = brandRepository.findByName(brandName); 116 | Model model = modelRepository.findByName(modelName); 117 | model.setBrand(brand); 118 | log.info("Adding model {} to brand {}", modelName, brandName); 119 | return modelRepository.save(model); 120 | } 121 | 122 | @Override 123 | public Long deleteModelFromBrand(String brandName, String modelName) { 124 | Model model = modelRepository.findByName(modelName); 125 | List cars = carRepository.findByModel(model); 126 | log.info("Brisanje modela {} iz brenda {} ", modelName, brandName ); 127 | if (cars.size() > 0) return null; 128 | Long id = model.getId(); 129 | modelRepository.deleteById(id); 130 | log.info("Delete a model"); 131 | return id; 132 | } 133 | 134 | @Override 135 | public List getFuelTypes() { 136 | log.info("Get fuel types"); 137 | return fuelRepository.findAll(); 138 | } 139 | 140 | @Override 141 | public Fuel saveFuel(String fuelType) { 142 | Fuel fuel = fuelRepository.findByName(fuelType); 143 | if (fuel != null) return null; 144 | log.info("Saving fuel {} to the database", fuelType); 145 | return fuelRepository.save(new Fuel(null, fuelType)); 146 | } 147 | 148 | @Override 149 | public Long deleteFuel(String fuelType) { 150 | Fuel fuel = fuelRepository.findByName(fuelType); 151 | List cars = carRepository.findByFuel(fuel); 152 | if (cars.size() > 0) return null; 153 | Long id = fuel.getId(); 154 | fuelRepository.deleteById(id); 155 | log.info("Delete a fuel type"); 156 | return id; 157 | } 158 | 159 | @Override 160 | public List getColors() { 161 | log.info("Get colors"); 162 | return colorRepository.findAll(); 163 | } 164 | 165 | @Override 166 | public Color saveColor(String colorName) { 167 | Color color = colorRepository.findByName(colorName); 168 | if (color != null) return null; 169 | log.info("Saving new color {} to the database", colorName); 170 | return colorRepository.save(new Color(null, colorName)); 171 | } 172 | 173 | @Override 174 | public Long deleteColor(String colorName) { 175 | Color color = colorRepository.findByName(colorName); 176 | List cars = carRepository.findByColor(color); 177 | if (cars.size() > 0) return null; 178 | Long id = color.getId(); 179 | colorRepository.deleteById(id); 180 | log.info("Delete a color"); 181 | return id; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /frontend/src/app/cars/car-edit/car-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { ActivatedRoute, ParamMap } from '@angular/router'; 4 | import { Subscription } from 'rxjs'; 5 | import { AdminService } from 'src/app/admin/admin.service'; 6 | import { AuthService } from 'src/app/auth/auth.service'; 7 | import { mimeType } from '../car-add/mime-type.validator'; 8 | import { CarsService } from '../cars.service'; 9 | 10 | @Component({ 11 | selector: 'app-car-edit', 12 | templateUrl: './car-edit.component.html', 13 | styleUrls: ['./car-edit.component.css'] 14 | }) 15 | export class CarEditComponent implements OnInit, OnDestroy { 16 | isLoading: boolean = false; 17 | form!: FormGroup; 18 | private carId!: number; 19 | imagePreview!: string; 20 | brands: string[] = []; 21 | models: string[] = []; 22 | fuelTypes: string[] = []; 23 | colors: string[] = []; 24 | years: number[] = []; 25 | private authStatusSub!: Subscription; 26 | private brandsSub!: Subscription; 27 | private modelsSub!: Subscription; 28 | private fuelTypesSub!: Subscription; 29 | private colorsSub!: Subscription; 30 | 31 | constructor(public carsService: CarsService, public route: ActivatedRoute, private authService: AuthService, private adminService: AdminService) { } 32 | 33 | ngOnInit(): void { 34 | let date: Date = new Date(); 35 | let finalYear = date.getFullYear(); 36 | for(let i = finalYear; i >= 1980; i--) { 37 | this.years.push(i); 38 | } 39 | 40 | this.authStatusSub = this.authService.getAuthStatusListener().subscribe( 41 | authStatus => { 42 | this.isLoading = false; 43 | } 44 | ); 45 | this.adminService.getBrands(); 46 | this.brandsSub = this.adminService.getBrandsUpdateListener() 47 | .subscribe((brandsData: {brands: string[]}) => { 48 | this.brands = brandsData.brands; 49 | }); 50 | 51 | this.modelsSub = this.adminService.getModelsUpdateListener() 52 | .subscribe((modelsData: {models: string[]}) => { 53 | this.models = modelsData.models; 54 | }); 55 | this.adminService.getFuelTypes(); 56 | this.fuelTypesSub = this.adminService.getFuelTypesUpdateListener() 57 | .subscribe((fuelTypesData: {fuelTypes: string[]}) => { 58 | this.fuelTypes = fuelTypesData.fuelTypes; 59 | }); 60 | this.adminService.getColors(); 61 | this.colorsSub = this.adminService.getColorsUpdateListener() 62 | .subscribe((colorsData: {colors: string[]}) => { 63 | this.colors = colorsData.colors; 64 | }); 65 | 66 | this.form = new FormGroup({ 67 | title: new FormControl(null, { 68 | validators: [Validators.required] 69 | }), 70 | brand: new FormControl(null, { 71 | validators: [Validators.required] 72 | }), 73 | model: new FormControl(null, { 74 | validators: [Validators.required] 75 | }), 76 | image: new FormControl(null, { 77 | validators: [Validators.required], 78 | asyncValidators: [mimeType] 79 | }), 80 | mileage: new FormControl(null, { 81 | validators: [ 82 | Validators.required, 83 | Validators.pattern("^\\d{1,6}$") 84 | ] 85 | }), 86 | registration: new FormControl(null, { 87 | validators: [Validators.required] 88 | }), 89 | price: new FormControl(null, { 90 | validators: [ 91 | Validators.required, 92 | Validators.pattern("^\\d{1,6}$") 93 | ] 94 | }), 95 | fuel: new FormControl(null, { 96 | validators: [Validators.required] 97 | }), 98 | color: new FormControl(null, { 99 | validators: [Validators.required] 100 | }), 101 | phone: new FormControl(null, { 102 | validators: [ 103 | Validators.required, 104 | Validators.pattern("^\\+381/?[0-9]{2}-?[0-9]{2}-?[0-9]{2}-?[0-9]{3}$") 105 | ] 106 | }), 107 | content: new FormControl(null, { 108 | validators: [Validators.required] 109 | }) 110 | }); 111 | this.route.paramMap.subscribe((paramMap: ParamMap) => { 112 | if(paramMap.has('carId')) { 113 | this.carId = Number(paramMap.get('carId')); 114 | this.isLoading = true; 115 | this.carsService.getCar(this.carId) 116 | .subscribe(carData => { 117 | this.adminService.getModels(carData.brand.name); 118 | this.imagePreview = carData.path; 119 | this.isLoading = false; 120 | console.log(carData); 121 | this.form.setValue({ 122 | title: carData.title, 123 | brand: carData.brand.name, 124 | model: carData.model.name, 125 | image: carData.path, 126 | mileage: carData.mileage, 127 | registration: carData.registration, 128 | price: carData.price, 129 | fuel: carData.fuel.name, 130 | color: carData.color.name, 131 | phone: carData.phone, 132 | content: carData.content 133 | }); 134 | }); 135 | } 136 | }); 137 | } 138 | 139 | onImagePicked(event: Event) { 140 | const files = (event.target as HTMLInputElement).files; 141 | if (files !== null) { 142 | const file = files[0]; 143 | this.form.patchValue({ 'image': file }); 144 | this.form.get('image')?.updateValueAndValidity(); 145 | const reader = new FileReader(); 146 | reader.onload = () => { 147 | this.imagePreview = reader.result as string; 148 | }; 149 | reader.readAsDataURL(file); 150 | } 151 | } 152 | 153 | onUpdateCar() { 154 | if (this.form.invalid) { 155 | return; 156 | } 157 | this.carsService.updateCar( 158 | this.carId, 159 | this.form.value.title, 160 | this.form.value.brand, 161 | this.form.value.model, 162 | this.form.value.image, 163 | this.form.value.mileage, 164 | this.form.value.registration, 165 | this.form.value.price, 166 | this.form.value.fuel, 167 | this.form.value.color, 168 | this.form.value.phone, 169 | this.form.value.content); 170 | this.form.reset(); 171 | } 172 | 173 | onChange(brandName: string) { 174 | this.adminService.getModels(brandName); 175 | this.form.get('model')?.patchValue(null); 176 | } 177 | 178 | ngOnDestroy(): void { 179 | this.authStatusSub.unsubscribe(); 180 | this.brandsSub.unsubscribe(); 181 | this.modelsSub.unsubscribe(); 182 | this.fuelTypesSub.unsubscribe(); 183 | this.colorsSub.unsubscribe(); 184 | } 185 | 186 | 187 | } 188 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/services/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.services; 2 | 3 | import java.time.Instant; 4 | import java.time.ZoneId; 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.security.core.Authentication; 13 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 14 | import org.springframework.security.core.context.SecurityContextHolder; 15 | import org.springframework.security.core.userdetails.UserDetails; 16 | import org.springframework.security.core.userdetails.UserDetailsService; 17 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 18 | import org.springframework.security.crypto.password.PasswordEncoder; 19 | import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; 20 | import org.springframework.stereotype.Service; 21 | 22 | import com.app.backend.models.Role; 23 | import com.app.backend.models.SignupData; 24 | import com.app.backend.models.User; 25 | import com.app.backend.repositories.RoleRepository; 26 | import com.app.backend.repositories.UserRepository; 27 | import com.auth0.jwt.JWT; 28 | import com.auth0.jwt.algorithms.Algorithm; 29 | import com.auth0.jwt.interfaces.DecodedJWT; 30 | import com.auth0.jwt.interfaces.JWTVerifier; 31 | 32 | import lombok.extern.slf4j.Slf4j; 33 | 34 | @Service 35 | @Slf4j 36 | public class UserServiceImpl implements UserService, UserDetailsService { 37 | @Autowired 38 | private UserRepository userRepository; 39 | @Autowired 40 | private RoleRepository roleRepository; 41 | @Autowired 42 | private PasswordEncoder passwordEncoder; 43 | 44 | @Override 45 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 46 | User user = userRepository.findByUsername(username); 47 | if (user == null) { 48 | log.error("User not found in the database"); 49 | throw new UsernameNotFoundException("User not found in the database"); 50 | } else { 51 | log.info("User found in the database: {}", username); 52 | Collection authorities = new ArrayList<>(); 53 | user.getRoles().forEach(role -> authorities.add(new SimpleGrantedAuthority(role.getName()))); 54 | return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities); 55 | } 56 | } 57 | 58 | @Override 59 | public boolean hasAdminPrivilege(String authorizationHeader) { 60 | String token = authorizationHeader.substring("Bearer ".length()); 61 | Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); 62 | JWTVerifier verifier = JWT.require(algorithm).build(); 63 | DecodedJWT decodedJWT = verifier.verify(token); 64 | String[] roles = decodedJWT.getClaim("roles").asArray(String.class); 65 | boolean hasPrivilege = false; 66 | for (int i = 0; i < roles.length; i++) { 67 | if ("ROLE_ADMIN".equals(roles[i])) { 68 | hasPrivilege = true; 69 | } 70 | } 71 | return hasPrivilege; 72 | } 73 | 74 | @Override 75 | public boolean isBlocked(Long userId) { 76 | return userRepository.getById(userId).getBanned(); 77 | } 78 | 79 | @Override 80 | public User getUserById(Long id) { 81 | log.info("Fetching user by id"); 82 | return userRepository.getById(id); 83 | } 84 | 85 | @Override 86 | public User getUser(String username) { 87 | log.info("Fetching user {}", username); 88 | return userRepository.findByUsername(username); 89 | } 90 | 91 | @Override 92 | public User getUserFromAuthorizationHeader(String authorizationHeader) { 93 | String token = authorizationHeader.substring("Bearer ".length()); 94 | Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); 95 | JWTVerifier verifier = JWT.require(algorithm).build(); 96 | DecodedJWT decodedJWT = verifier.verify(token); 97 | String username = decodedJWT.getSubject(); 98 | return getUser(username); 99 | } 100 | 101 | @Override 102 | public User signupUser(SignupData signupData) { 103 | log.info("Signup user {}", signupData.getFirstName()); 104 | User user = getUser(signupData.getUsername()); 105 | if (user != null) return null; 106 | user = new User(); 107 | user.setFirstName(signupData.getFirstName()); 108 | user.setLastName(signupData.getLastName()); 109 | user.setUsername(signupData.getUsername()); 110 | user.setPassword(signupData.getPassword()); 111 | user.setBrithdate(Instant.ofEpochMilli(signupData.getTimestamp()).atZone(ZoneId.systemDefault()).toLocalDate()); 112 | user.setCity(signupData.getCity()); 113 | user.setCountry(signupData.getCountry()); 114 | user.setBanned(false); 115 | User savedUser = saveUser(user); 116 | addRoleToUser(savedUser.getUsername(), "ROLE_USER"); 117 | return savedUser; 118 | } 119 | 120 | @Override 121 | public User changePassword(Long userId, String oldPassword, String newPassword) { 122 | User user = getUserById(userId); 123 | if (passwordEncoder.matches(oldPassword, user.getPassword())) { 124 | log.info("Password is changed"); 125 | user.setPassword(passwordEncoder.encode(newPassword)); 126 | return userRepository.save(user); 127 | } 128 | log.info("Password isn't changed"); 129 | return null; 130 | } 131 | 132 | @Override 133 | public User updateUser(User user, String firstName, String lastName, Long timestamp, String city, String country) { 134 | log.info("Update user data"); 135 | user.setFirstName(firstName); 136 | user.setLastName(lastName); 137 | user.setBrithdate(Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDate()); 138 | user.setCity(city); 139 | user.setCountry(country); 140 | return userRepository.save(user); 141 | } 142 | 143 | @Override 144 | public Boolean logoutUser(HttpServletRequest request, HttpServletResponse response) { 145 | Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 146 | SecurityContextLogoutHandler handler = null; 147 | if (auth != null) { 148 | handler = new SecurityContextLogoutHandler(); 149 | handler.logout(request, response, auth); 150 | } 151 | return handler.isInvalidateHttpSession(); 152 | } 153 | 154 | @Override 155 | public User saveUser(User user) { 156 | log.info("Saving new user {} to the database", user.getFirstName()); 157 | user.setPassword(passwordEncoder.encode(user.getPassword())); 158 | return userRepository.save(user); 159 | } 160 | 161 | @Override 162 | public Role saveRole(Role role) { 163 | log.info("Saving new role {} to the database", role.getName()); 164 | return roleRepository.save(role); 165 | } 166 | 167 | @Override 168 | public void addRoleToUser(String username, String roleName) { 169 | log.info("Adding role {} to user {}", roleName, username); 170 | User user = userRepository.findByUsername(username); 171 | Role role = roleRepository.findByName(roleName); 172 | user.getRoles().add(role); 173 | userRepository.save(user); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /backend/src/main/java/com/app/backend/services/CarServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.app.backend.services; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.apache.tika.Tika; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.PageRequest; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.util.StringUtils; 14 | import org.springframework.web.multipart.MultipartFile; 15 | 16 | import com.app.backend.models.Brand; 17 | import com.app.backend.models.Car; 18 | import com.app.backend.models.Color; 19 | import com.app.backend.models.FrontCar; 20 | import com.app.backend.models.Fuel; 21 | import com.app.backend.models.Model; 22 | import com.app.backend.models.User; 23 | import com.app.backend.repositories.BrandRepository; 24 | import com.app.backend.repositories.CarRepository; 25 | import com.app.backend.repositories.ColorRepository; 26 | import com.app.backend.repositories.FuelRepository; 27 | import com.app.backend.repositories.ModelRepository; 28 | import com.app.backend.util.CarResponse; 29 | import com.app.backend.util.CarUrlName; 30 | import com.app.backend.util.FileUploadUtil; 31 | import com.fasterxml.jackson.databind.ObjectMapper; 32 | 33 | import lombok.extern.slf4j.Slf4j; 34 | 35 | @Service 36 | @Slf4j 37 | public class CarServiceImpl implements CarService { 38 | @Autowired 39 | private CarRepository carRepository; 40 | @Autowired 41 | private BrandRepository brandRepository; 42 | @Autowired 43 | private ModelRepository modelRepository; 44 | @Autowired 45 | private FuelRepository fuelRepository; 46 | @Autowired 47 | private ColorRepository colorRepository; 48 | 49 | @SuppressWarnings("serial") 50 | private Map mimeTypes = new HashMap() { 51 | { 52 | put("image/png", "png"); 53 | put("image/jpeg", "jpg"); 54 | put("image/jpg", "jpg"); 55 | } 56 | }; 57 | 58 | @Override 59 | public Car addCar(MultipartFile image, String carData, User user) throws IOException { 60 | CarUrlName temp = new CarUrlName(); 61 | getCarFileName(image, carData, temp); 62 | temp.getCar().setOwner(user); 63 | Car savedCar = carRepository.save(temp.getCar()); 64 | saveImage(temp.getFileName(), image); 65 | log.info("Add car"); 66 | return savedCar; 67 | } 68 | 69 | @Override 70 | public Car getCar(Long id) { 71 | log.info("Get car"); 72 | return carRepository.getById(id); 73 | } 74 | 75 | @Override 76 | public CarResponse getCars(Integer page, Integer pagesize) { 77 | Pageable pageable = PageRequest.of(page, pagesize); 78 | Page cars = carRepository.findAll(pageable); 79 | CarResponse response = new CarResponse(); 80 | response.setCars(cars.getContent()); 81 | response.setCount(cars.getTotalElements()); 82 | log.info("Get cars"); 83 | return response; 84 | } 85 | 86 | @Override 87 | public CarResponse getMyCars(User user, Integer page, Integer pagesize) { 88 | Pageable pageable = PageRequest.of(page, pagesize); 89 | Page cars = carRepository.findByOwner(user, pageable); 90 | CarResponse response = new CarResponse(); 91 | response.setCars(cars.getContent()); 92 | response.setCount(cars.getTotalElements()); 93 | log.info("Get my cars"); 94 | return response; 95 | } 96 | 97 | @Override 98 | public Car updateCarImage(Long id, MultipartFile image, String carData, User user) throws IOException { 99 | CarUrlName temp = new CarUrlName(); 100 | getCarFileName(image, carData, temp); 101 | Car c = carRepository.getById(id); 102 | Car newCar = temp.getCar(); 103 | c.setTitle(newCar.getTitle()); 104 | c.setBrand(newCar.getBrand()); 105 | c.setModel(newCar.getModel()); 106 | c.setPath(newCar.getPath()); 107 | c.setMileage(newCar.getMileage()); 108 | c.setRegistration(newCar.getRegistration()); 109 | c.setPrice(newCar.getPrice()); 110 | c.setFuel(newCar.getFuel()); 111 | c.setColor(newCar.getColor()); 112 | c.setPhone(newCar.getPhone()); 113 | c.setContent(newCar.getContent()); 114 | if (user.getId() != c.getOwner().getId()) return null; 115 | Car savedCar = carRepository.save(c); 116 | saveImage(temp.getFileName(), image); 117 | log.info("Update car image"); 118 | return savedCar; 119 | } 120 | 121 | @Override 122 | public Car updateCarNoImage(Long id, FrontCar frontCar, User user) { 123 | Car c = carRepository.getById(id); 124 | if (user.getId() != c.getOwner().getId()) return null; 125 | c.setTitle(frontCar.getTitle()); 126 | Brand brand = brandRepository.findByName(frontCar.getBrand()); 127 | c.setBrand(brand); 128 | Model model = modelRepository.findByName(frontCar.getModel()); 129 | c.setModel(model); 130 | c.setMileage(frontCar.getMileage()); 131 | c.setRegistration(frontCar.getRegistration()); 132 | c.setPrice(frontCar.getPrice()); 133 | Fuel fuel = fuelRepository.findByName(frontCar.getFuel()); 134 | c.setFuel(fuel); 135 | Color color = colorRepository.findByName(frontCar.getColor()); 136 | c.setColor(color); 137 | c.setPhone(frontCar.getPhone()); 138 | c.setContent(frontCar.getContent()); 139 | Car savedCar = carRepository.save(c); 140 | log.info("Update car no image"); 141 | return savedCar; 142 | } 143 | 144 | @Override 145 | public Long deleteCar(Long id, User user) { 146 | Car p = carRepository.getById(id); 147 | if (user.getId() != p.getOwner().getId()) return null; 148 | carRepository.deleteById(id); 149 | log.info("Delete car"); 150 | return id; 151 | } 152 | 153 | private void getCarFileName(MultipartFile image, String carData, CarUrlName temp) throws IOException { 154 | Car car = getCarFromString(carData); 155 | temp.setCar(car); 156 | String name = StringUtils.cleanPath(image.getOriginalFilename()); 157 | Tika tika = new Tika(); 158 | String ext = mimeTypes.get(tika.detect(image.getBytes())); 159 | temp.setFileName(name + "-" + System.currentTimeMillis() + "." + ext); 160 | temp.getCar().setPath("http://localhost:8081/images/" + temp.getFileName()); 161 | } 162 | 163 | private void saveImage(String fileName, MultipartFile image) throws IOException { 164 | String uploadDir = "images"; 165 | FileUploadUtil.saveFile(uploadDir, fileName, image); 166 | } 167 | 168 | private Car getCarFromString(String carData) throws IOException { 169 | FrontCar frontCar = new ObjectMapper().readValue(carData, FrontCar.class); 170 | Car car = new Car(); 171 | car.setId(frontCar.getId()); 172 | car.setTitle(frontCar.getTitle()); 173 | Brand brand = brandRepository.findByName(frontCar.getBrand()); 174 | car.setBrand(brand); 175 | Model model = modelRepository.findByName(frontCar.getModel()); 176 | car.setModel(model); 177 | car.setMileage(frontCar.getMileage()); 178 | car.setRegistration(frontCar.getRegistration()); 179 | car.setPrice(frontCar.getPrice()); 180 | Fuel fuel = fuelRepository.findByName(frontCar.getFuel()); 181 | car.setFuel(fuel); 182 | Color color = colorRepository.findByName(frontCar.getColor()); 183 | car.setColor(color); 184 | log.info(frontCar.getPhone()); 185 | car.setPhone(frontCar.getPhone()); 186 | car.setContent(frontCar.getContent()); 187 | return car; 188 | } 189 | } 190 | --------------------------------------------------------------------------------