devctl 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. devctl/__init__.py +3 -0
  2. devctl/commands/__init__.py +3 -0
  3. devctl/commands/add.py +166 -0
  4. devctl/commands/deploy.py +61 -0
  5. devctl/commands/docker.py +65 -0
  6. devctl/commands/init.py +193 -0
  7. devctl/commands/run.py +67 -0
  8. devctl/generators/__init__.py +3 -0
  9. devctl/generators/angular.py +112 -0
  10. devctl/generators/django.py +61 -0
  11. devctl/generators/docker_scaffold.py +656 -0
  12. devctl/generators/fastapi.py +67 -0
  13. devctl/generators/go_fiber.py +61 -0
  14. devctl/generators/nestjs.py +49 -0
  15. devctl/generators/nextjs.py +53 -0
  16. devctl/generators/nodejs.py +109 -0
  17. devctl/generators/react.py +43 -0
  18. devctl/generators/scaffold_angular.py +163 -0
  19. devctl/generators/scaffold_django.py +79 -0
  20. devctl/generators/scaffold_fastapi.py +83 -0
  21. devctl/generators/scaffold_go.py +67 -0
  22. devctl/generators/scaffold_nestjs.py +52 -0
  23. devctl/generators/scaffold_nextjs.py +73 -0
  24. devctl/generators/scaffold_nodejs.py +81 -0
  25. devctl/generators/scaffold_react.py +80 -0
  26. devctl/generators/scaffold_spring.py +166 -0
  27. devctl/generators/scaffold_svelte.py +73 -0
  28. devctl/generators/scaffold_vue.py +111 -0
  29. devctl/generators/spring.py +221 -0
  30. devctl/generators/svelte.py +52 -0
  31. devctl/generators/vue.py +105 -0
  32. devctl/main.py +45 -0
  33. devctl/orchestrator/__init__.py +3 -0
  34. devctl/orchestrator/config_builder.py +64 -0
  35. devctl/orchestrator/runner.py +219 -0
  36. devctl/orchestrator/scanner.py +155 -0
  37. devctl/templates/angular/config/environment.development.ts.j2 +4 -0
  38. devctl/templates/angular/config/environment.ts.j2 +5 -0
  39. devctl/templates/angular/config/proxy.conf.json.j2 +8 -0
  40. devctl/templates/angular/feature/models/request.model.ts.j2 +5 -0
  41. devctl/templates/angular/feature/models/response.model.ts.j2 +6 -0
  42. devctl/templates/angular/feature/pages/form/form.component.html.j2 +21 -0
  43. devctl/templates/angular/feature/pages/form/form.component.scss.j2 +0 -0
  44. devctl/templates/angular/feature/pages/form/form.component.ts.j2 +63 -0
  45. devctl/templates/angular/feature/pages/list/list.component.html.j2 +28 -0
  46. devctl/templates/angular/feature/pages/list/list.component.scss.j2 +0 -0
  47. devctl/templates/angular/feature/pages/list/list.component.ts.j2 +34 -0
  48. devctl/templates/angular/feature/routes.ts.j2 +9 -0
  49. devctl/templates/angular/feature/services/service.ts.j2 +34 -0
  50. devctl/templates/docker/deploy.yml.j2 +37 -0
  51. devctl/templates/docker/django/Dockerfile.j2 +21 -0
  52. devctl/templates/docker/fastapi/Dockerfile.j2 +15 -0
  53. devctl/templates/docker/frontend/Dockerfile.j2 +31 -0
  54. devctl/templates/docker/go/Dockerfile.j2 +24 -0
  55. devctl/templates/docker/nestjs/Dockerfile.j2 +26 -0
  56. devctl/templates/docker/nextjs/Dockerfile.j2 +43 -0
  57. devctl/templates/docker/nodejs/Dockerfile.j2 +24 -0
  58. devctl/templates/docker/spring/Dockerfile.j2 +24 -0
  59. devctl/templates/docker/svelte/Dockerfile.j2 +24 -0
  60. devctl/templates/proxy.conf.json.j2 +0 -0
  61. devctl/templates/spring/Controller.java.j2 +50 -0
  62. devctl/templates/spring/Entity.java.j2 +22 -0
  63. devctl/templates/spring/Repository.java.j2 +9 -0
  64. devctl/templates/spring/Service.java.j2 +20 -0
  65. devctl/templates/spring/ServiceImpl.java.j2 +62 -0
  66. devctl/templates/spring/application.properties.j2 +19 -0
  67. devctl/templates/spring/config/ApplicationConfig.java.j2 +46 -0
  68. devctl/templates/spring/config/JwtAuthenticationFilter.java.j2 +54 -0
  69. devctl/templates/spring/config/JwtService.java.j2 +68 -0
  70. devctl/templates/spring/config/SecurityConfig.java.j2 +42 -0
  71. devctl/templates/spring/docker-compose.yml.j2 +29 -0
  72. devctl/templates/spring/dto/Request.java.j2 +20 -0
  73. devctl/templates/spring/dto/Response.java.j2 +22 -0
  74. devctl/templates/spring/mapper/Mapper.java.j2 +30 -0
  75. devctl/templates/vue/config/App.vue.j2 +35 -0
  76. devctl/templates/vue/config/main.ts.j2 +9 -0
  77. devctl/templates/vue/config/router.ts.j2 +18 -0
  78. devctl/templates/vue/config/vite.config.ts.j2 +16 -0
  79. devctl/templates/vue/feature/Form.vue.j2 +162 -0
  80. devctl/templates/vue/feature/List.vue.j2 +155 -0
  81. devctl/templates/vue/feature/models.ts.j2 +12 -0
  82. devctl/templates/vue/feature/routes.ts.j2 +19 -0
  83. devctl/templates/vue/feature/service.ts.j2 +44 -0
  84. devctl/utils/__init__.py +3 -0
  85. devctl/utils/dependencies.py +36 -0
  86. devctl/utils/env_loader.py +57 -0
  87. devctl-1.0.0.dist-info/METADATA +127 -0
  88. devctl-1.0.0.dist-info/RECORD +92 -0
  89. devctl-1.0.0.dist-info/WHEEL +5 -0
  90. devctl-1.0.0.dist-info/entry_points.txt +2 -0
  91. devctl-1.0.0.dist-info/licenses/LICENSE +21 -0
  92. devctl-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,15 @@
1
+ # Production Dockerfile for FastAPI
2
+ # Generated by devctl
3
+
4
+ FROM python:3.11-slim
5
+
6
+ WORKDIR /app
7
+
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ COPY . .
12
+
13
+ EXPOSE 8000
14
+
15
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
@@ -0,0 +1,31 @@
1
+ # syntax=docker/dockerfile:1.7
2
+ # Generated by devctl dockerize.
3
+ # {{ project.kind|capitalize }} image: Node build stage + Nginx static runtime.
4
+
5
+ FROM node:{{ project.node_version }}-alpine AS build
6
+ WORKDIR /app
7
+
8
+ COPY package*.json ./
9
+ RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
10
+
11
+ COPY . .
12
+ {% if project.kind == "angular" %}
13
+ RUN npm run build \
14
+ && mkdir -p /tmp/devctl-dist \
15
+ && if [ -d "dist/{{ project.angular_output_name }}/browser" ]; then cp -r "dist/{{ project.angular_output_name }}/browser/." /tmp/devctl-dist/; \
16
+ elif [ -d "dist/{{ project.angular_output_name }}" ]; then cp -r "dist/{{ project.angular_output_name }}/." /tmp/devctl-dist/; \
17
+ elif [ -d "dist" ]; then FIRST_DIST_DIR="$(find dist -mindepth 1 -maxdepth 2 -type d | head -n 1)" && test -n "$FIRST_DIST_DIR" && cp -r "$FIRST_DIST_DIR/." /tmp/devctl-dist/; \
18
+ else echo "Unable to find Angular build output under dist/" >&2; exit 1; fi
19
+ {% else %}
20
+ RUN npm run build \
21
+ && mkdir -p /tmp/devctl-dist \
22
+ && test -d dist \
23
+ && cp -r dist/. /tmp/devctl-dist/
24
+ {% endif %}
25
+
26
+ FROM nginx:1.27-alpine
27
+
28
+ COPY --from=build /tmp/devctl-dist/ /usr/share/nginx/html/
29
+
30
+ EXPOSE 80
31
+ CMD ["nginx", "-g", "daemon off;"]
@@ -0,0 +1,24 @@
1
+ # Production Dockerfile for Go (Fiber)
2
+ # Generated by devctl
3
+
4
+ # Stage 1: Build
5
+ FROM golang:1.21-alpine AS builder
6
+
7
+ WORKDIR /app
8
+
9
+ COPY go.mod go.sum ./
10
+ RUN go mod download
11
+
12
+ COPY . .
13
+ RUN CGO_ENABLED=0 GOOS=linux go build -o main .
14
+
15
+ # Stage 2: Run
16
+ FROM alpine:latest
17
+
18
+ WORKDIR /app
19
+
20
+ COPY --from=builder /app/main .
21
+
22
+ EXPOSE 3000
23
+
24
+ CMD ["./main"]
@@ -0,0 +1,26 @@
1
+ # Production Dockerfile for NestJS
2
+ # Generated by devctl
3
+
4
+ # Stage 1: Build
5
+ FROM node:{{ project.node_version }}-alpine AS builder
6
+
7
+ WORKDIR /app
8
+
9
+ COPY package*.json ./
10
+ RUN npm install
11
+
12
+ COPY . .
13
+ RUN npm run build
14
+
15
+ # Stage 2: Run
16
+ FROM node:{{ project.node_version }}-alpine
17
+
18
+ WORKDIR /app
19
+
20
+ COPY --from=builder /app/package*.json ./
21
+ COPY --from=builder /app/node_modules ./node_modules
22
+ COPY --from=builder /app/dist ./dist
23
+
24
+ EXPOSE 3000
25
+
26
+ CMD ["node", "dist/main"]
@@ -0,0 +1,43 @@
1
+ # Production Dockerfile for NextJS
2
+ # Generated by devctl
3
+
4
+ FROM node:{{ project.node_version }}-alpine AS base
5
+
6
+ # 1. Install dependencies only when needed
7
+ FROM base AS deps
8
+ RUN apk add --no-cache libc6-compat
9
+ WORKDIR /app
10
+ COPY package*.json ./
11
+ RUN npm install
12
+
13
+ # 2. Rebuild the source code only when needed
14
+ FROM base AS builder
15
+ WORKDIR /app
16
+ COPY --from=deps /app/node_modules ./node_modules
17
+ COPY . .
18
+ RUN npm run build
19
+
20
+ # 3. Production image, copy all the files and run next
21
+ FROM base AS runner
22
+ WORKDIR /app
23
+
24
+ ENV NODE_ENV=production
25
+
26
+ RUN addgroup -g 1001 -S nodejs
27
+ RUN adduser -S nextjs -u 1001
28
+
29
+ COPY --from=builder /app/public ./public
30
+
31
+ # Automatically leverage output traces to reduce image size
32
+ # https://nextjs.org/docs/advanced-features/output-file-tracing
33
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
34
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
35
+
36
+ USER nextjs
37
+
38
+ EXPOSE 3000
39
+
40
+ ENV PORT=3000
41
+ ENV HOSTNAME="0.0.0.0"
42
+
43
+ CMD ["node", "server.js"]
@@ -0,0 +1,24 @@
1
+ # Production Dockerfile for NodeJS/Express
2
+ # Generated by devctl
3
+
4
+ FROM node:{{ project.node_version }}-alpine AS builder
5
+
6
+ WORKDIR /app
7
+
8
+ COPY package*.json ./
9
+ RUN npm install
10
+
11
+ COPY . .
12
+ RUN npm run build
13
+
14
+ FROM node:{{ project.node_version }}-alpine
15
+
16
+ WORKDIR /app
17
+
18
+ COPY --from=builder /app/package*.json ./
19
+ COPY --from=builder /app/node_modules ./node_modules
20
+ COPY --from=builder /app/dist ./dist
21
+
22
+ EXPOSE 3000
23
+
24
+ CMD ["npm", "start"]
@@ -0,0 +1,24 @@
1
+ # syntax=docker/dockerfile:1.7
2
+ # Generated by devctl dockerize.
3
+ # Production Spring Boot image: Maven build stage + lean non-root JRE runtime.
4
+
5
+ FROM maven:3.9-eclipse-temurin-{{ project.java_version }} AS build
6
+ WORKDIR /workspace
7
+
8
+ COPY . .
9
+ RUN if [ -f mvnw ]; then chmod +x mvnw && ./mvnw -B -ntp clean package -DskipTests; else mvn -B -ntp clean package -DskipTests; fi
10
+ RUN JAR_FILE="$(find target -maxdepth 1 -type f -name '*.jar' ! -name '*sources.jar' ! -name '*javadoc.jar' | head -n 1)" \
11
+ && test -n "$JAR_FILE" \
12
+ && cp "$JAR_FILE" /tmp/app.jar
13
+
14
+ FROM eclipse-temurin:{{ project.java_version }}-jre-alpine
15
+ WORKDIR /app
16
+
17
+ RUN addgroup -S app && adduser -S app -G app
18
+ COPY --from=build /tmp/app.jar /app/app.jar
19
+
20
+ USER app
21
+ EXPOSE 8080
22
+ ENV JAVA_OPTS=""
23
+
24
+ ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
@@ -0,0 +1,24 @@
1
+ # Production Dockerfile for SvelteKit
2
+ # Generated by devctl
3
+
4
+ FROM node:{{ project.node_version }}-alpine AS builder
5
+
6
+ WORKDIR /app
7
+
8
+ COPY package*.json ./
9
+ RUN npm install
10
+
11
+ COPY . .
12
+ RUN npm run build
13
+
14
+ FROM node:{{ project.node_version }}-alpine
15
+
16
+ WORKDIR /app
17
+
18
+ COPY --from=builder /app/package*.json ./
19
+ COPY --from=builder /app/node_modules ./node_modules
20
+ COPY --from=builder /app/build ./build
21
+
22
+ EXPOSE 3000
23
+
24
+ CMD ["node", "build"]
File without changes
@@ -0,0 +1,50 @@
1
+ package {{ base_package }}.controller;
2
+
3
+ import {{ base_package }}.dto.request.{{ entity_name }}Request;
4
+ import {{ base_package }}.dto.response.{{ entity_name }}Response;
5
+ import {{ base_package }}.service.{{ entity_name }}Service;
6
+ import org.springframework.http.HttpStatus;
7
+ import org.springframework.http.ResponseEntity;
8
+ import org.springframework.web.bind.annotation.*;
9
+
10
+ import java.util.List;
11
+
12
+ @RestController
13
+ @RequestMapping("/api/{{ table_name }}")
14
+ @CrossOrigin(origins = "*")
15
+ public class {{ class_name }} {
16
+
17
+ private final {{ entity_name }}Service service;
18
+
19
+ public {{ class_name }}({{ entity_name }}Service service) {
20
+ this.service = service;
21
+ }
22
+
23
+ @GetMapping
24
+ public ResponseEntity<List<{{ entity_name }}Response>> getAll() {
25
+ return ResponseEntity.ok(service.findAll());
26
+ }
27
+
28
+ @GetMapping("/{id}")
29
+ public ResponseEntity<{{ entity_name }}Response> getById(@PathVariable Long id) {
30
+ return service.findById(id)
31
+ .map(ResponseEntity::ok)
32
+ .orElse(ResponseEntity.notFound().build());
33
+ }
34
+
35
+ @PostMapping
36
+ public ResponseEntity<{{ entity_name }}Response> create(@RequestBody {{ entity_name }}Request request) {
37
+ return new ResponseEntity<>(service.save(request), HttpStatus.CREATED);
38
+ }
39
+
40
+ @PutMapping("/{id}")
41
+ public ResponseEntity<{{ entity_name }}Response> update(@PathVariable Long id, @RequestBody {{ entity_name }}Request request) {
42
+ return ResponseEntity.ok(service.update(id, request));
43
+ }
44
+
45
+ @DeleteMapping("/{id}")
46
+ public ResponseEntity<Void> delete(@PathVariable Long id) {
47
+ service.deleteById(id);
48
+ return ResponseEntity.noContent().build();
49
+ }
50
+ }
@@ -0,0 +1,22 @@
1
+ package {{ base_package }}.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import lombok.AllArgsConstructor;
5
+ import lombok.Data;
6
+ import lombok.NoArgsConstructor;
7
+
8
+ @Entity
9
+ @Table(name = "{{ table_name }}")
10
+ @Data
11
+ @NoArgsConstructor
12
+ @AllArgsConstructor
13
+ public class {{ class_name }} {
14
+
15
+ @Id
16
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
17
+ private Long id;
18
+
19
+ {% for field in fields %}
20
+ private {{ field.java_type }} {{ field.name }};
21
+ {% endfor %}
22
+ }
@@ -0,0 +1,9 @@
1
+ package {{ base_package }}.repository;
2
+
3
+ import {{ base_package }}.entity.{{ entity_name }}Entity;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.stereotype.Repository;
6
+
7
+ @Repository
8
+ public interface {{ class_name }} extends JpaRepository<{{ entity_name }}Entity, Long> {
9
+ }
@@ -0,0 +1,20 @@
1
+ package {{ base_package }}.service;
2
+
3
+ import {{ base_package }}.dto.request.{{ entity_name }}Request;
4
+ import {{ base_package }}.dto.response.{{ entity_name }}Response;
5
+ import java.util.List;
6
+ import java.util.Optional;
7
+
8
+ public interface {{ class_name }} {
9
+
10
+ List<{{ entity_name }}Response> findAll();
11
+
12
+ Optional<{{ entity_name }}Response> findById(Long id);
13
+
14
+ {{ entity_name }}Response save({{ entity_name }}Request request);
15
+
16
+ // Ajout de la méthode update pour exploiter le @MappingTarget de MapStruct
17
+ {{ entity_name }}Response update(Long id, {{ entity_name }}Request request);
18
+
19
+ void deleteById(Long id);
20
+ }
@@ -0,0 +1,62 @@
1
+ package {{ base_package }}.service.impl;
2
+
3
+ import {{ base_package }}.dto.request.{{ entity_name }}Request;
4
+ import {{ base_package }}.dto.response.{{ entity_name }}Response;
5
+ import {{ base_package }}.entity.{{ entity_name }}Entity;
6
+ import {{ base_package }}.mapper.{{ entity_name }}Mapper;
7
+ import {{ base_package }}.repository.{{ entity_name }}Repository;
8
+ import {{ base_package }}.service.{{ entity_name }}Service;
9
+ import lombok.RequiredArgsConstructor;
10
+ import org.springframework.stereotype.Service;
11
+ import org.springframework.transaction.annotation.Transactional;
12
+
13
+ import java.util.List;
14
+ import java.util.Optional;
15
+ import java.util.stream.Collectors;
16
+
17
+ @Service
18
+ @RequiredArgsConstructor
19
+ public class {{ class_name }} implements {{ entity_name }}Service {
20
+
21
+ private final {{ entity_name }}Repository repository;
22
+ private final {{ entity_name }}Mapper mapper;
23
+
24
+ @Override
25
+ public List<{{ entity_name }}Response> findAll() {
26
+ return repository.findAll().stream()
27
+ .map(mapper::toResponse)
28
+ .collect(Collectors.toList());
29
+ }
30
+
31
+ @Override
32
+ public Optional<{{ entity_name }}Response> findById(Long id) {
33
+ return repository.findById(id)
34
+ .map(mapper::toResponse);
35
+ }
36
+
37
+ @Override
38
+ @Transactional
39
+ public {{ entity_name }}Response save({{ entity_name }}Request request) {
40
+ {{ entity_name }}Entity entity = mapper.toEntity(request);
41
+ {{ entity_name }}Entity savedEntity = repository.save(entity);
42
+ return mapper.toResponse(savedEntity);
43
+ }
44
+
45
+ @Override
46
+ @Transactional
47
+ public {{ entity_name }}Response update(Long id, {{ entity_name }}Request request) {
48
+ return repository.findById(id)
49
+ .map(existingEntity -> {
50
+ mapper.updateEntityFromRequest(request, existingEntity);
51
+ {{ entity_name }}Entity updatedEntity = repository.save(existingEntity);
52
+ return mapper.toResponse(updatedEntity);
53
+ })
54
+ .orElseThrow(() -> new RuntimeException("{{ entity_name }} not found with id: " + id));
55
+ }
56
+
57
+ @Override
58
+ @Transactional
59
+ public void deleteById(Long id) {
60
+ repository.deleteById(id);
61
+ }
62
+ }
@@ -0,0 +1,19 @@
1
+ spring.application.name={{ project_name }}
2
+
3
+ spring.datasource.username={{ db_user }}
4
+ spring.datasource.password={{ db_password }}
5
+ spring.jpa.hibernate.ddl-auto=update
6
+ spring.jpa.show-sql=true
7
+
8
+ {% if db_type == 'postgres' %}
9
+ spring.datasource.url=jdbc:postgresql://localhost:{{ db_port }}/{{ db_name }}
10
+ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
11
+ {% elif db_type == 'mysql' %}
12
+ spring.datasource.url=jdbc:mysql://localhost:{{ db_port }}/{{ db_name }}
13
+ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
14
+ {% elif db_type == 'mongodb' %}
15
+ spring.data.mongodb.uri=mongodb://{{ db_user }}:{{ db_password }}@localhost:{{ db_port }}/{{ db_name }}?authSource=admin
16
+ {% endif %}
17
+
18
+ application.security.jwt.secret-key={{ jwt_secret|default('404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970') }}
19
+ application.security.jwt.expiration={{ jwt_expiration|default('86400000') }}
@@ -0,0 +1,46 @@
1
+ package {{ base_package }}.config;
2
+
3
+ import lombok.RequiredArgsConstructor;
4
+ import org.springframework.context.annotation.Bean;
5
+ import org.springframework.context.annotation.Configuration;
6
+ import org.springframework.security.authentication.AuthenticationManager;
7
+ import org.springframework.security.authentication.AuthenticationProvider;
8
+ import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
9
+ import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
10
+ import org.springframework.security.core.userdetails.UserDetailsService;
11
+ import org.springframework.security.core.userdetails.UsernameNotFoundException;
12
+ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
13
+ import org.springframework.security.crypto.password.PasswordEncoder;
14
+
15
+ @Configuration
16
+ @RequiredArgsConstructor
17
+ public class ApplicationConfig {
18
+
19
+ // Attention: nécessite la création d'un UserRepository plus tard
20
+ // private final UserRepository repository;
21
+
22
+ @Bean
23
+ public UserDetailsService userDetailsService() {
24
+ return username -> {
25
+ // Logique à implémenter selon ton UserRepository
26
+ throw new UsernameNotFoundException("User not found");
27
+ };
28
+ }
29
+
30
+ @Bean
31
+ public AuthenticationProvider authenticationProvider() {
32
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService());
33
+ authProvider.setPasswordEncoder(passwordEncoder());
34
+ return authProvider;
35
+ }
36
+
37
+ @Bean
38
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
39
+ return config.getAuthenticationManager();
40
+ }
41
+
42
+ @Bean
43
+ public PasswordEncoder passwordEncoder() {
44
+ return new BCryptPasswordEncoder();
45
+ }
46
+ }
@@ -0,0 +1,54 @@
1
+ package {{ base_package }}.config;
2
+
3
+ import jakarta.servlet.FilterChain;
4
+ import jakarta.servlet.ServletException;
5
+ import jakarta.servlet.http.HttpServletRequest;
6
+ import jakarta.servlet.http.HttpServletResponse;
7
+ import java.io.IOException;
8
+ import lombok.RequiredArgsConstructor;
9
+ import org.springframework.lang.NonNull;
10
+ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
11
+ import org.springframework.security.core.context.SecurityContextHolder;
12
+ import org.springframework.security.core.userdetails.UserDetails;
13
+ import org.springframework.security.core.userdetails.UserDetailsService;
14
+ import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
15
+ import org.springframework.stereotype.Component;
16
+ import org.springframework.web.filter.OncePerRequestFilter;
17
+
18
+ @Component
19
+ @RequiredArgsConstructor
20
+ public class JwtAuthenticationFilter extends OncePerRequestFilter {
21
+
22
+ private final JwtService jwtService;
23
+ private final UserDetailsService userDetailsService;
24
+
25
+ @Override
26
+ protected void doFilterInternal(
27
+ @NonNull HttpServletRequest request,
28
+ @NonNull HttpServletResponse response,
29
+ @NonNull FilterChain filterChain
30
+ ) throws ServletException, IOException {
31
+ final String authHeader = request.getHeader("Authorization");
32
+ final String jwt;
33
+ final String userEmail;
34
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
35
+ filterChain.doFilter(request, response);
36
+ return;
37
+ }
38
+ jwt = authHeader.substring(7);
39
+ userEmail = jwtService.extractUsername(jwt);
40
+ if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
41
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
42
+ if (jwtService.isTokenValid(jwt, userDetails)) {
43
+ UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
44
+ userDetails,
45
+ null,
46
+ userDetails.getAuthorities()
47
+ );
48
+ authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
49
+ SecurityContextHolder.getContext().setAuthentication(authToken);
50
+ }
51
+ }
52
+ filterChain.doFilter(request, response);
53
+ }
54
+ }
@@ -0,0 +1,68 @@
1
+ package {{ base_package }}.config;
2
+
3
+ import io.jsonwebtoken.Claims;
4
+ import io.jsonwebtoken.Jwts;
5
+ import io.jsonwebtoken.io.Decoders;
6
+ import io.jsonwebtoken.security.Keys;
7
+ import org.springframework.beans.factory.annotation.Value;
8
+ import org.springframework.security.core.userdetails.UserDetails;
9
+ import org.springframework.stereotype.Service;
10
+ import javax.crypto.SecretKey;
11
+ import java.util.Date;
12
+ import java.util.Map;
13
+ import java.util.HashMap;
14
+ import java.util.function.Function;
15
+
16
+ @Service
17
+ public class JwtService {
18
+
19
+ @Value("${application.security.jwt.secret-key}")
20
+ private String secretKey;
21
+ @Value("${application.security.jwt.expiration}")
22
+ private long jwtExpiration;
23
+
24
+ public String extractUsername(String token) {
25
+ return extractClaim(token, Claims::getSubject);
26
+ }
27
+
28
+ public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
29
+ final Claims claims = extractAllClaims(token);
30
+ return claimsResolver.apply(claims);
31
+ }
32
+
33
+ public String generateToken(UserDetails userDetails) {
34
+ return generateToken(new HashMap<>(), userDetails);
35
+ }
36
+
37
+ public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
38
+ return Jwts.builder()
39
+ .claims(extraClaims)
40
+ .subject(userDetails.getUsername())
41
+ .issuedAt(new Date(System.currentTimeMillis()))
42
+ .expiration(new Date(System.currentTimeMillis() + jwtExpiration))
43
+ .signWith(getSignInKey(), Jwts.SIG.HS256)
44
+ .compact();
45
+ }
46
+
47
+ public boolean isTokenValid(String token, UserDetails userDetails) {
48
+ final String username = extractUsername(token);
49
+ return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
50
+ }
51
+
52
+ private boolean isTokenExpired(String token) {
53
+ return extractClaim(token, Claims::getExpiration).before(new Date());
54
+ }
55
+
56
+ private Claims extractAllClaims(String token) {
57
+ return Jwts.parser()
58
+ .verifyWith(getSignInKey())
59
+ .build()
60
+ .parseSignedClaims(token)
61
+ .getPayload();
62
+ }
63
+
64
+ private SecretKey getSignInKey() {
65
+ byte[] keyBytes = Decoders.BASE64.decode(secretKey);
66
+ return Keys.hmacShaKeyFor(keyBytes);
67
+ }
68
+ }
@@ -0,0 +1,42 @@
1
+ package {{ base_package }}.config;
2
+
3
+ import lombok.RequiredArgsConstructor;
4
+ import org.springframework.context.annotation.Bean;
5
+ import org.springframework.context.annotation.Configuration;
6
+ import org.springframework.security.authentication.AuthenticationProvider;
7
+ import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
8
+ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
9
+ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
10
+ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
11
+ import org.springframework.security.config.http.SessionCreationPolicy;
12
+ import org.springframework.security.web.SecurityFilterChain;
13
+ import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
14
+
15
+ @Configuration
16
+ @EnableWebSecurity
17
+ @EnableMethodSecurity
18
+ @RequiredArgsConstructor
19
+ public class SecurityConfig {
20
+
21
+ private final JwtAuthenticationFilter jwtAuthFilter;
22
+ private final AuthenticationProvider authenticationProvider;
23
+
24
+ @Bean
25
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
26
+ http
27
+ .csrf(AbstractHttpConfigurer::disable)
28
+ // Désactivation totale du CORS Spring car Nginx agit comme Reverse Proxy (Single Origin)
29
+ .cors(AbstractHttpConfigurer::disable)
30
+ .authorizeHttpRequests(auth -> auth
31
+ // Routes publiques standards
32
+ .requestMatchers("/api/v1/auth/**", "/v3/api-docs/**", "/swagger-ui/**").permitAll()
33
+ // Sécurisation du reste de l'API
34
+ .anyRequest().authenticated()
35
+ )
36
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
37
+ .authenticationProvider(authenticationProvider)
38
+ .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
39
+
40
+ return http.build();
41
+ }
42
+ }
@@ -0,0 +1,29 @@
1
+ services:
2
+ {{ db_service_name }}:
3
+ {% if db_type == 'postgres' %}
4
+ image: postgres:15-alpine
5
+ environment:
6
+ POSTGRES_USER: {{ db_user }}
7
+ POSTGRES_PASSWORD: {{ db_password }}
8
+ POSTGRES_DB: {{ db_name }}
9
+ {% elif db_type == 'mysql' %}
10
+ image: mysql:8.0
11
+ environment:
12
+ MYSQL_ROOT_PASSWORD: {{ db_password }}
13
+ MYSQL_DATABASE: {{ db_name }}
14
+ MYSQL_USER: {{ db_user }}
15
+ MYSQL_PASSWORD: {{ db_password }}
16
+ {% elif db_type == 'mongodb' %}
17
+ image: mongo:6.0
18
+ environment:
19
+ MONGO_INITDB_ROOT_USERNAME: {{ db_user }}
20
+ MONGO_INITDB_ROOT_PASSWORD: {{ db_password }}
21
+ MONGO_INITDB_DATABASE: {{ db_name }}
22
+ {% endif %}
23
+ ports:
24
+ - "{{ db_port }}:{% if db_type == 'postgres' %}5432{% elif db_type == 'mysql' %}3306{% else %}27017{% endif %}"
25
+ volumes:
26
+ - {{ db_name }}_data:{% if db_type == 'postgres' %}/var/lib/postgresql/data{% elif db_type == 'mysql' %}/var/lib/mysql/data{% else %}/data/db{% endif %}
27
+
28
+ volumes:
29
+ {{ db_name }}_data:
@@ -0,0 +1,20 @@
1
+ package {{ base_package }}.dto.request;
2
+
3
+ import lombok.AllArgsConstructor;
4
+ import lombok.Builder;
5
+ import lombok.Data;
6
+ import lombok.NoArgsConstructor;
7
+
8
+ /**
9
+ * Data Transfer Object for creating or updating a {{ entity_name }}.
10
+ */
11
+ @Data
12
+ @Builder
13
+ @NoArgsConstructor
14
+ @AllArgsConstructor
15
+ public class {{ entity_name }}Request {
16
+
17
+ {% for field in fields %}
18
+ private {{ field.java_type }} {{ field.name }};
19
+ {% endfor %}
20
+ }