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.
- devctl/__init__.py +3 -0
- devctl/commands/__init__.py +3 -0
- devctl/commands/add.py +166 -0
- devctl/commands/deploy.py +61 -0
- devctl/commands/docker.py +65 -0
- devctl/commands/init.py +193 -0
- devctl/commands/run.py +67 -0
- devctl/generators/__init__.py +3 -0
- devctl/generators/angular.py +112 -0
- devctl/generators/django.py +61 -0
- devctl/generators/docker_scaffold.py +656 -0
- devctl/generators/fastapi.py +67 -0
- devctl/generators/go_fiber.py +61 -0
- devctl/generators/nestjs.py +49 -0
- devctl/generators/nextjs.py +53 -0
- devctl/generators/nodejs.py +109 -0
- devctl/generators/react.py +43 -0
- devctl/generators/scaffold_angular.py +163 -0
- devctl/generators/scaffold_django.py +79 -0
- devctl/generators/scaffold_fastapi.py +83 -0
- devctl/generators/scaffold_go.py +67 -0
- devctl/generators/scaffold_nestjs.py +52 -0
- devctl/generators/scaffold_nextjs.py +73 -0
- devctl/generators/scaffold_nodejs.py +81 -0
- devctl/generators/scaffold_react.py +80 -0
- devctl/generators/scaffold_spring.py +166 -0
- devctl/generators/scaffold_svelte.py +73 -0
- devctl/generators/scaffold_vue.py +111 -0
- devctl/generators/spring.py +221 -0
- devctl/generators/svelte.py +52 -0
- devctl/generators/vue.py +105 -0
- devctl/main.py +45 -0
- devctl/orchestrator/__init__.py +3 -0
- devctl/orchestrator/config_builder.py +64 -0
- devctl/orchestrator/runner.py +219 -0
- devctl/orchestrator/scanner.py +155 -0
- devctl/templates/angular/config/environment.development.ts.j2 +4 -0
- devctl/templates/angular/config/environment.ts.j2 +5 -0
- devctl/templates/angular/config/proxy.conf.json.j2 +8 -0
- devctl/templates/angular/feature/models/request.model.ts.j2 +5 -0
- devctl/templates/angular/feature/models/response.model.ts.j2 +6 -0
- devctl/templates/angular/feature/pages/form/form.component.html.j2 +21 -0
- devctl/templates/angular/feature/pages/form/form.component.scss.j2 +0 -0
- devctl/templates/angular/feature/pages/form/form.component.ts.j2 +63 -0
- devctl/templates/angular/feature/pages/list/list.component.html.j2 +28 -0
- devctl/templates/angular/feature/pages/list/list.component.scss.j2 +0 -0
- devctl/templates/angular/feature/pages/list/list.component.ts.j2 +34 -0
- devctl/templates/angular/feature/routes.ts.j2 +9 -0
- devctl/templates/angular/feature/services/service.ts.j2 +34 -0
- devctl/templates/docker/deploy.yml.j2 +37 -0
- devctl/templates/docker/django/Dockerfile.j2 +21 -0
- devctl/templates/docker/fastapi/Dockerfile.j2 +15 -0
- devctl/templates/docker/frontend/Dockerfile.j2 +31 -0
- devctl/templates/docker/go/Dockerfile.j2 +24 -0
- devctl/templates/docker/nestjs/Dockerfile.j2 +26 -0
- devctl/templates/docker/nextjs/Dockerfile.j2 +43 -0
- devctl/templates/docker/nodejs/Dockerfile.j2 +24 -0
- devctl/templates/docker/spring/Dockerfile.j2 +24 -0
- devctl/templates/docker/svelte/Dockerfile.j2 +24 -0
- devctl/templates/proxy.conf.json.j2 +0 -0
- devctl/templates/spring/Controller.java.j2 +50 -0
- devctl/templates/spring/Entity.java.j2 +22 -0
- devctl/templates/spring/Repository.java.j2 +9 -0
- devctl/templates/spring/Service.java.j2 +20 -0
- devctl/templates/spring/ServiceImpl.java.j2 +62 -0
- devctl/templates/spring/application.properties.j2 +19 -0
- devctl/templates/spring/config/ApplicationConfig.java.j2 +46 -0
- devctl/templates/spring/config/JwtAuthenticationFilter.java.j2 +54 -0
- devctl/templates/spring/config/JwtService.java.j2 +68 -0
- devctl/templates/spring/config/SecurityConfig.java.j2 +42 -0
- devctl/templates/spring/docker-compose.yml.j2 +29 -0
- devctl/templates/spring/dto/Request.java.j2 +20 -0
- devctl/templates/spring/dto/Response.java.j2 +22 -0
- devctl/templates/spring/mapper/Mapper.java.j2 +30 -0
- devctl/templates/vue/config/App.vue.j2 +35 -0
- devctl/templates/vue/config/main.ts.j2 +9 -0
- devctl/templates/vue/config/router.ts.j2 +18 -0
- devctl/templates/vue/config/vite.config.ts.j2 +16 -0
- devctl/templates/vue/feature/Form.vue.j2 +162 -0
- devctl/templates/vue/feature/List.vue.j2 +155 -0
- devctl/templates/vue/feature/models.ts.j2 +12 -0
- devctl/templates/vue/feature/routes.ts.j2 +19 -0
- devctl/templates/vue/feature/service.ts.j2 +44 -0
- devctl/utils/__init__.py +3 -0
- devctl/utils/dependencies.py +36 -0
- devctl/utils/env_loader.py +57 -0
- devctl-1.0.0.dist-info/METADATA +127 -0
- devctl-1.0.0.dist-info/RECORD +92 -0
- devctl-1.0.0.dist-info/WHEEL +5 -0
- devctl-1.0.0.dist-info/entry_points.txt +2 -0
- devctl-1.0.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
}
|