create-backlist 6.0.0 → 6.0.2

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 (45) hide show
  1. package/bin/backlist.js +227 -0
  2. package/package.json +10 -4
  3. package/src/analyzer.js +210 -89
  4. package/src/db/prisma.ts +4 -0
  5. package/src/generators/dotnet.js +120 -94
  6. package/src/generators/java.js +157 -109
  7. package/src/generators/node.js +262 -85
  8. package/src/generators/template.js +38 -2
  9. package/src/scanner/index.js +99 -0
  10. package/src/templates/dotnet/partials/Controller.cs.ejs +7 -14
  11. package/src/templates/dotnet/partials/Dto.cs.ejs +8 -0
  12. package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +7 -2
  13. package/src/templates/java-spring/partials/AuthController.java.ejs +23 -10
  14. package/src/templates/java-spring/partials/Controller.java.ejs +17 -6
  15. package/src/templates/java-spring/partials/Dockerfile.ejs +6 -1
  16. package/src/templates/java-spring/partials/Entity.java.ejs +15 -5
  17. package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +30 -7
  18. package/src/templates/java-spring/partials/JwtService.java.ejs +38 -10
  19. package/src/templates/java-spring/partials/Repository.java.ejs +10 -1
  20. package/src/templates/java-spring/partials/Service.java.ejs +45 -7
  21. package/src/templates/java-spring/partials/User.java.ejs +17 -4
  22. package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +10 -4
  23. package/src/templates/java-spring/partials/UserRepository.java.ejs +8 -0
  24. package/src/templates/java-spring/partials/docker-compose.yml.ejs +16 -8
  25. package/src/templates/node-ts-express/base/server.ts +12 -5
  26. package/src/templates/node-ts-express/base/tsconfig.json +13 -3
  27. package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +17 -7
  28. package/src/templates/node-ts-express/partials/App.test.ts.ejs +27 -27
  29. package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +56 -62
  30. package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +21 -10
  31. package/src/templates/node-ts-express/partials/Controller.ts.ejs +40 -40
  32. package/src/templates/node-ts-express/partials/DbContext.cs.ejs +3 -3
  33. package/src/templates/node-ts-express/partials/Dockerfile.ejs +9 -11
  34. package/src/templates/node-ts-express/partials/Model.cs.ejs +25 -7
  35. package/src/templates/node-ts-express/partials/Model.ts.ejs +20 -12
  36. package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +72 -55
  37. package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +27 -12
  38. package/src/templates/node-ts-express/partials/README.md.ejs +9 -12
  39. package/src/templates/node-ts-express/partials/Seeder.ts.ejs +44 -64
  40. package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +31 -16
  41. package/src/templates/node-ts-express/partials/package.json.ejs +3 -1
  42. package/src/templates/node-ts-express/partials/prismaClient.ts.ejs +4 -0
  43. package/src/templates/node-ts-express/partials/routes.ts.ejs +35 -24
  44. package/src/utils.js +19 -4
  45. package/bin/index.js +0 -141
@@ -4,22 +4,27 @@ package <%= group %>.<%= projectName %>;
4
4
  import org.springframework.boot.CommandLineRunner;
5
5
  import org.springframework.context.annotation.Bean;
6
6
  import org.springframework.context.annotation.Configuration;
7
+
7
8
  import <%= group %>.<%= projectName %>.model.User;
8
9
  import <%= group %>.<%= projectName %>.repository.UserRepository;
10
+
9
11
  import org.springframework.security.crypto.password.PasswordEncoder;
10
12
 
11
13
  @Configuration
12
14
  public class ApplicationSeeder {
15
+
13
16
  @Bean
14
17
  CommandLineRunner seed(UserRepository userRepository, PasswordEncoder encoder) {
15
18
  return args -> {
16
- if (userRepository.findByEmail("admin@example.com").isEmpty()) {
19
+ userRepository.findByEmail("admin@example.com").ifPresentOrElse(u -> {
20
+ // already exists
21
+ }, () -> {
17
22
  User admin = new User();
18
23
  admin.setName("Admin");
19
24
  admin.setEmail("admin@example.com");
20
25
  admin.setPassword(encoder.encode("admin123"));
21
26
  userRepository.save(admin);
22
- }
27
+ });
23
28
  };
24
29
  }
25
30
  }
@@ -4,6 +4,7 @@ package <%= group %>.<%= projectName %>.controller;
4
4
  import <%= group %>.<%= projectName %>.model.User;
5
5
  import <%= group %>.<%= projectName %>.repository.UserRepository;
6
6
  import <%= group %>.<%= projectName %>.security.JwtService;
7
+
7
8
  import org.springframework.http.ResponseEntity;
8
9
  import org.springframework.http.HttpStatus;
9
10
  import org.springframework.security.crypto.password.PasswordEncoder;
@@ -21,29 +22,41 @@ public class AuthController {
21
22
  private final JwtService jwt;
22
23
 
23
24
  public AuthController(UserRepository repo, PasswordEncoder encoder, JwtService jwt) {
24
- this.repo = repo; this.encoder = encoder; this.jwt = jwt;
25
+ this.repo = repo;
26
+ this.encoder = encoder;
27
+ this.jwt = jwt;
25
28
  }
26
29
 
27
30
  @PostMapping("/register")
28
- public ResponseEntity<?> register(@RequestBody User req) {
29
- if (repo.findByEmail(req.getEmail()).isPresent()) {
31
+ public ResponseEntity<?> register(@RequestBody AuthRequest req) {
32
+ if (repo.findByEmail(req.email()).isPresent()) {
30
33
  return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("User already exists");
31
34
  }
32
- req.setPassword(encoder.encode(req.getPassword()));
33
- repo.save(req);
34
- String token = jwt.generateToken(req.getEmail());
35
+
36
+ User user = new User();
37
+ user.setName(req.name());
38
+ user.setEmail(req.email());
39
+ user.setPassword(encoder.encode(req.password()));
40
+ repo.save(user);
41
+
42
+ String token = jwt.generateToken(user.getEmail());
35
43
  return ResponseEntity.status(HttpStatus.CREATED).body(new TokenResponse(token));
36
44
  }
37
45
 
38
46
  @PostMapping("/login")
39
- public ResponseEntity<?> login(@RequestBody User req) {
40
- Optional<User> current = repo.findByEmail(req.getEmail());
47
+ public ResponseEntity<?> login(@RequestBody LoginRequest req) {
48
+ Optional<User> current = repo.findByEmail(req.email());
41
49
  if (current.isEmpty()) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid credentials");
42
- if (!encoder.matches(req.getPassword(), current.get().getPassword()))
50
+
51
+ if (!encoder.matches(req.password(), current.get().getPassword())) {
43
52
  return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid credentials");
44
- String token = jwt.generateToken(req.getEmail());
53
+ }
54
+
55
+ String token = jwt.generateToken(current.get().getEmail());
45
56
  return ResponseEntity.ok(new TokenResponse(token));
46
57
  }
47
58
 
59
+ public record AuthRequest(String name, String email, String password) {}
60
+ public record LoginRequest(String email, String password) {}
48
61
  public record TokenResponse(String token) {}
49
62
  }
@@ -4,23 +4,32 @@ package <%= group %>.<%= projectName %>.controller;
4
4
  import org.springframework.http.ResponseEntity;
5
5
  import org.springframework.http.HttpStatus;
6
6
  import org.springframework.web.bind.annotation.*;
7
+
7
8
  import java.util.List;
8
- import java.util.Optional;
9
+
9
10
  import <%= group %>.<%= projectName %>.service.<%= controllerName %>Service;
10
11
  import <%= group %>.<%= projectName %>.model.<%= controllerName %>;
11
12
 
12
13
  @RestController
13
- @RequestMapping("/api/<%= controllerName.toLowerCase() %>s")
14
+ @RequestMapping("<%= basePath %>")
14
15
  @CrossOrigin(origins = "*")
15
16
  public class <%= controllerName %>Controller {
16
17
  private final <%= controllerName %>Service service;
17
- public <%= controllerName %>Controller(<%= controllerName %>Service service) { this.service = service; }
18
18
 
19
- @GetMapping public List<<%= controllerName %>> all() { return service.findAll(); }
19
+ public <%= controllerName %>Controller(<%= controllerName %>Service service) {
20
+ this.service = service;
21
+ }
22
+
23
+ @GetMapping
24
+ public List<<%= controllerName %>> all() {
25
+ return service.findAll();
26
+ }
20
27
 
21
28
  @GetMapping("/{id}")
22
29
  public ResponseEntity<<%= controllerName %>> one(@PathVariable Long id) {
23
- return service.findById(id).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
30
+ return service.findById(id)
31
+ .map(ResponseEntity::ok)
32
+ .orElse(ResponseEntity.notFound().build());
24
33
  }
25
34
 
26
35
  @PostMapping
@@ -30,7 +39,9 @@ public class <%= controllerName %>Controller {
30
39
 
31
40
  @PutMapping("/{id}")
32
41
  public ResponseEntity<<%= controllerName %>> update(@PathVariable Long id, @RequestBody <%= controllerName %> m) {
33
- return service.update(id, m).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());
42
+ return service.update(id, m)
43
+ .map(ResponseEntity::ok)
44
+ .orElse(ResponseEntity.notFound().build());
34
45
  }
35
46
 
36
47
  @DeleteMapping("/{id}")
@@ -1,7 +1,12 @@
1
1
  # Auto-generated by create-backlist
2
2
  FROM eclipse-temurin:21-jdk AS build
3
3
  WORKDIR /app
4
- COPY . .
4
+
5
+ COPY .mvn/ .mvn/
6
+ COPY mvnw pom.xml ./
7
+ RUN chmod +x mvnw && ./mvnw -q -DskipTests dependency:go-offline
8
+
9
+ COPY src ./src
5
10
  RUN ./mvnw -q -DskipTests package
6
11
 
7
12
  FROM eclipse-temurin:21-jre
@@ -2,14 +2,24 @@
2
2
  package <%= group %>.<%= projectName %>.model;
3
3
 
4
4
  import jakarta.persistence.*;
5
- import lombok.Data;
5
+ import lombok.*;
6
6
 
7
7
  @Data
8
+ @NoArgsConstructor
9
+ @AllArgsConstructor
8
10
  @Entity
11
+ @Table(name = "<%= modelName.toLowerCase() %>")
9
12
  public class <%= modelName %> {
10
- @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
13
+
14
+ @Id
15
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
11
16
  private Long id;
12
- <% model.fields.forEach(f => { %>
13
- private <%= f.type === 'Number' ? 'Integer' : (f.type === 'Boolean' ? 'Boolean' : 'String') %> <%= f.name %>;
14
- <% }) %>
17
+
18
+ <% model.fields.forEach(f => { -%>
19
+ private <%- (f.type === 'number' || f.type === 'Number') ? 'Double'
20
+ : (f.type === 'int' || f.type === 'Integer') ? 'Integer'
21
+ : (f.type === 'boolean' || f.type === 'Boolean') ? 'Boolean'
22
+ : (f.type === 'date' || f.type === 'Date') ? 'java.time.Instant'
23
+ : 'String' %> <%= f.name %>;
24
+ <% }) -%>
15
25
  }
@@ -5,6 +5,9 @@ import jakarta.servlet.FilterChain;
5
5
  import jakarta.servlet.ServletException;
6
6
  import jakarta.servlet.http.HttpServletRequest;
7
7
  import jakarta.servlet.http.HttpServletResponse;
8
+
9
+ import io.jsonwebtoken.JwtException;
10
+
8
11
  import org.springframework.security.core.userdetails.UserDetailsService;
9
12
  import org.springframework.security.core.userdetails.UserDetails;
10
13
  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -25,19 +28,39 @@ public class JwtAuthFilter extends OncePerRequestFilter {
25
28
  this.uds = uds;
26
29
  }
27
30
 
31
+ @Override
32
+ protected boolean shouldNotFilter(HttpServletRequest request) {
33
+ String path = request.getRequestURI();
34
+ return path != null && path.startsWith("/api/auth");
35
+ }
36
+
28
37
  @Override
29
38
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
30
39
  throws ServletException, IOException {
40
+
41
+ // If already authenticated, skip
42
+ if (SecurityContextHolder.getContext().getAuthentication() != null) {
43
+ chain.doFilter(request, response);
44
+ return;
45
+ }
46
+
31
47
  String header = request.getHeader("Authorization");
48
+
32
49
  if (header != null && header.startsWith("Bearer ")) {
33
- String token = header.substring(7);
34
- try {
35
- String subject = jwtService.validateAndGetSubject(token);
36
- UserDetails ud = uds.loadUserByUsername(subject);
37
- var auth = new UsernamePasswordAuthenticationToken(ud, null, ud.getAuthorities());
38
- SecurityContextHolder.getContext().setAuthentication(auth);
39
- } catch (Exception ignored) {}
50
+ String token = header.substring(7).trim();
51
+ if (!token.isEmpty()) {
52
+ try {
53
+ String subject = jwtService.validateAndGetSubject(token);
54
+ UserDetails ud = uds.loadUserByUsername(subject);
55
+
56
+ var auth = new UsernamePasswordAuthenticationToken(ud, null, ud.getAuthorities());
57
+ SecurityContextHolder.getContext().setAuthentication(auth);
58
+ } catch (JwtException ex) {
59
+ // Invalid/expired token: continue without authentication
60
+ }
61
+ }
40
62
  }
63
+
41
64
  chain.doFilter(request, response);
42
65
  }
43
66
  }
@@ -5,26 +5,54 @@ import io.jsonwebtoken.*;
5
5
  import io.jsonwebtoken.security.Keys;
6
6
  import org.springframework.stereotype.Service;
7
7
 
8
+ import java.nio.charset.StandardCharsets;
8
9
  import java.security.Key;
9
10
  import java.util.Date;
10
11
 
11
12
  @Service
12
13
  public class JwtService {
13
- private final Key key = Keys.hmacShaKeyFor(System.getenv("JWT_SECRET") != null
14
- ? System.getenv("JWT_SECRET").getBytes()
15
- : "change_this_dev_secret_change_this_dev_secret".getBytes());
16
14
 
17
- public String generateToken(String userId) {
15
+ private final Key key;
16
+ private final long expirationMs;
17
+
18
+ public JwtService() {
19
+ String secret = System.getenv("JWT_SECRET");
20
+ if (secret == null || secret.trim().length() < 32) {
21
+ // Must be >= 32 chars for HS256 keys (jjwt enforces minimum)
22
+ secret = "change_this_dev_secret_change_this_dev_secret";
23
+ }
24
+ this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
25
+
26
+ String exp = System.getenv("JWT_EXPIRES_MS");
27
+ long fallback = 1000L * 60 * 60 * 5; // 5 hours
28
+ this.expirationMs = (exp != null) ? parseLongSafe(exp, fallback) : fallback;
29
+ }
30
+
31
+ public String generateToken(String subject) {
32
+ Date now = new Date();
33
+ Date exp = new Date(now.getTime() + expirationMs);
34
+
18
35
  return Jwts.builder()
19
- .setSubject(userId)
20
- .setIssuedAt(new Date())
21
- .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 5))
36
+ .setSubject(subject)
37
+ .setIssuedAt(now)
38
+ .setExpiration(exp)
22
39
  .signWith(key, SignatureAlgorithm.HS256)
23
40
  .compact();
24
41
  }
25
42
 
26
- public String validateAndGetSubject(String token) {
27
- return Jwts.parserBuilder().setSigningKey(key).build()
28
- .parseClaimsJws(token).getBody().getSubject();
43
+ /**
44
+ * @throws JwtException if token invalid/expired
45
+ */
46
+ public String validateAndGetSubject(String token) throws JwtException {
47
+ return Jwts.parserBuilder()
48
+ .setSigningKey(key)
49
+ .build()
50
+ .parseClaimsJws(token)
51
+ .getBody()
52
+ .getSubject();
53
+ }
54
+
55
+ private long parseLongSafe(String v, long fallback) {
56
+ try { return Long.parseLong(v); } catch (Exception e) { return fallback; }
29
57
  }
30
58
  }
@@ -3,7 +3,16 @@ package <%= group %>.<%= projectName %>.repository;
3
3
 
4
4
  import org.springframework.data.jpa.repository.JpaRepository;
5
5
  import org.springframework.stereotype.Repository;
6
+
6
7
  import <%= group %>.<%= projectName %>.model.<%= modelName %>;
7
8
 
8
9
  @Repository
9
- public interface <%= modelName %>Repository extends JpaRepository<<%= modelName %>, Long> {}
10
+ public interface <%= modelName %>Repository extends JpaRepository<<%= modelName %>, Long> {
11
+
12
+ <%
13
+ // If generator passes `extraFinders: [{name:'Email', type:'String'}]`
14
+ (extraFinders || []).forEach(f => {
15
+ -%>
16
+ java.util.Optional<<%= modelName %>> findBy<%= f.name %>(<%= f.type %> <%= f.name.charAt(0).toLowerCase() + f.name.slice(1) %>);
17
+ <% }) -%>
18
+ }
@@ -2,27 +2,65 @@
2
2
  package <%= group %>.<%= projectName %>.service;
3
3
 
4
4
  import org.springframework.stereotype.Service;
5
+
5
6
  import java.util.List;
6
7
  import java.util.Optional;
8
+
7
9
  import <%= group %>.<%= projectName %>.repository.<%= modelName %>Repository;
8
10
  import <%= group %>.<%= projectName %>.model.<%= modelName %>;
9
11
 
10
12
  @Service
11
13
  public class <%= modelName %>Service {
14
+
12
15
  private final <%= modelName %>Repository repo;
13
- public <%= modelName %>Service(<%= modelName %>Repository repo) { this.repo = repo; }
14
16
 
15
- public List<<%= modelName %>> findAll() { return repo.findAll(); }
16
- public Optional<<%= modelName %>> findById(Long id) { return repo.findById(id); }
17
- public <%= modelName %> create(<%= modelName %> m) { return repo.save(m); }
17
+ public <%= modelName %>Service(<%= modelName %>Repository repo) {
18
+ this.repo = repo;
19
+ }
20
+
21
+ public List<<%= modelName %>> findAll() {
22
+ return repo.findAll();
23
+ }
24
+
25
+ public Optional<<%= modelName %>> findById(Long id) {
26
+ return repo.findById(id);
27
+ }
28
+
29
+ public <%= modelName %> create(<%= modelName %> m) {
30
+ // Ensure id is not forced by client
31
+ m.setId(null);
32
+ return repo.save(m);
33
+ }
34
+
18
35
  public Optional<<%= modelName %>> update(Long id, <%= modelName %> m) {
19
36
  return repo.findById(id).map(existing -> {
20
- <% model.fields.forEach(f => { %>
21
- existing.set<%= f.name.charAt(0).toUpperCase() + f.name.slice(1) %>(m.get<%= f.name.charAt(0).toUpperCase() + f.name.slice(1) %>());
22
- <% }) %>
37
+ <% model.fields.forEach(f => {
38
+ const cap = f.name.charAt(0).toUpperCase() + f.name.slice(1);
39
+ -%>
40
+ // Update <%= f.name %>
41
+ existing.set<%= cap %>(m.get<%= cap %>());
42
+ <% }) -%>
43
+ return repo.save(existing);
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Partial update: only set non-null values.
49
+ * Useful if your frontend sends partial payloads.
50
+ */
51
+ public Optional<<%= modelName %>> patch(Long id, <%= modelName %> m) {
52
+ return repo.findById(id).map(existing -> {
53
+ <% model.fields.forEach(f => {
54
+ const cap = f.name.charAt(0).toUpperCase() + f.name.slice(1);
55
+ -%>
56
+ if (m.get<%= cap %>() != null) {
57
+ existing.set<%= cap %>(m.get<%= cap %>());
58
+ }
59
+ <% }) -%>
23
60
  return repo.save(existing);
24
61
  });
25
62
  }
63
+
26
64
  public boolean delete(Long id) {
27
65
  if (!repo.existsById(id)) return false;
28
66
  repo.deleteById(id);
@@ -1,20 +1,33 @@
1
1
  // Auto-generated by create-backlist
2
2
  package <%= group %>.<%= projectName %>.model;
3
3
 
4
+ import com.fasterxml.jackson.annotation.JsonIgnore;
4
5
  import jakarta.persistence.*;
5
- import lombok.Data;
6
+ import lombok.*;
6
7
 
7
8
  @Data
9
+ @NoArgsConstructor
10
+ @AllArgsConstructor
8
11
  @Entity
9
- @Table(name="users")
12
+ @Table(
13
+ name = "users",
14
+ indexes = {
15
+ @Index(name = "idx_users_email", columnList = "email", unique = true)
16
+ }
17
+ )
10
18
  public class User {
11
- @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
19
+
20
+ @Id
21
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
12
22
  private Long id;
13
23
 
24
+ @Column(nullable = false)
14
25
  private String name;
15
26
 
16
- @Column(unique = true)
27
+ @Column(nullable = false, unique = true)
17
28
  private String email;
18
29
 
30
+ @JsonIgnore
31
+ @Column(nullable = false)
19
32
  private String password;
20
33
  }
@@ -3,12 +3,12 @@ package <%= group %>.<%= projectName %>.security;
3
3
 
4
4
  import <%= group %>.<%= projectName %>.model.User;
5
5
  import <%= group %>.<%= projectName %>.repository.UserRepository;
6
+
6
7
  import org.springframework.security.core.userdetails.UserDetailsService;
7
8
  import org.springframework.security.core.userdetails.UserDetails;
8
9
  import org.springframework.security.core.userdetails.UsernameNotFoundException;
10
+
9
11
  import org.springframework.stereotype.Service;
10
- import org.springframework.security.core.userdetails.User.UserBuilder;
11
- import org.springframework.security.core.userdetails.User.*;
12
12
 
13
13
  @Service
14
14
  public class UserDetailsServiceImpl implements UserDetailsService {
@@ -21,7 +21,13 @@ public class UserDetailsServiceImpl implements UserDetailsService {
21
21
 
22
22
  @Override
23
23
  public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
24
- User u = repo.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("Not found"));
25
- return withUsername(u.getEmail()).password(u.getPassword()).authorities("USER").build();
24
+ User u = repo.findByEmail(email)
25
+ .orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
26
+
27
+ return org.springframework.security.core.userdetails.User
28
+ .withUsername(u.getEmail())
29
+ .password(u.getPassword())
30
+ .authorities("ROLE_USER")
31
+ .build();
26
32
  }
27
33
  }
@@ -2,11 +2,19 @@
2
2
  package <%= group %>.<%= projectName %>.repository;
3
3
 
4
4
  import java.util.Optional;
5
+
5
6
  import org.springframework.data.jpa.repository.JpaRepository;
6
7
  import org.springframework.stereotype.Repository;
8
+
7
9
  import <%= group %>.<%= projectName %>.model.User;
8
10
 
9
11
  @Repository
10
12
  public interface UserRepository extends JpaRepository<User, Long> {
13
+
11
14
  Optional<User> findByEmail(String email);
15
+
16
+ boolean existsByEmail(String email);
17
+
18
+ // Optional: helpful if emails are stored normalized but requests vary
19
+ Optional<User> findByEmailIgnoreCase(String email);
12
20
  }
@@ -1,4 +1,5 @@
1
- version: '3.8'
1
+ version: "3.8"
2
+
2
3
  services:
3
4
  db:
4
5
  image: postgres:16-alpine
@@ -7,21 +8,28 @@ services:
7
8
  POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
8
9
  POSTGRES_DB: ${DB_NAME:-<%= projectName %>}
9
10
  ports:
10
- - "5432:5432"
11
+ - "${DB_PORT:-5432}:5432"
11
12
  volumes:
12
13
  - pgdata:/var/lib/postgresql/data
14
+ healthcheck:
15
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-<%= projectName %>}"]
16
+ interval: 5s
17
+ timeout: 5s
18
+ retries: 20
13
19
 
14
20
  app:
15
21
  build: .
16
22
  depends_on:
17
- - db
23
+ db:
24
+ condition: service_healthy
18
25
  environment:
19
- - JWT_SECRET=${JWT_SECRET:-change_me_long_secret}
20
- - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/${DB_NAME:-<%= projectName %>}
21
- - SPRING_DATASOURCE_USERNAME=${DB_USER:-postgres}
22
- - SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD:-password}
26
+ JWT_SECRET: ${JWT_SECRET:-change_me_long_secret_change_me_long_secret}
27
+ SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/${DB_NAME:-<%= projectName %>}
28
+ SPRING_DATASOURCE_USERNAME: ${DB_USER:-postgres}
29
+ SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-password}
30
+ SPRING_JPA_HIBERNATE_DDL_AUTO: ${JPA_DDL_AUTO:-update}
23
31
  ports:
24
- - "8080:8080"
32
+ - "${APP_PORT:-8080}:8080"
25
33
 
26
34
  volumes:
27
35
  pgdata:
@@ -4,19 +4,26 @@ import dotenv from 'dotenv';
4
4
 
5
5
  dotenv.config();
6
6
 
7
- const app: Express = express(); // Added :Express type
7
+ const app: Express = express();
8
8
 
9
9
  app.use(cors());
10
10
  app.use(express.json());
11
11
 
12
- app.get('/', (req: Request, res: Response) => { // Added :Request and :Response types
12
+ app.get('/', (req: Request, res: Response) => {
13
13
  res.send('Node.js Backend says Hello! Generated by Backlist.');
14
14
  });
15
15
 
16
- // INJECT:ROUTES
16
+ // <backlist:imports>
17
+ // </backlist:imports>
17
18
 
18
- const PORT: number | string = process.env.PORT || 8000; // Added type for PORT
19
+ // <backlist:setup>
20
+ // </backlist:setup>
21
+
22
+ // <backlist:routes>
23
+ // </backlist:routes>
24
+
25
+ const PORT: number | string = process.env.PORT || 8000;
19
26
 
20
27
  app.listen(PORT, () => {
21
- console.log(`⚡️ Server running on http://localhost:${PORT}`);
28
+ console.log(`Server running on http://localhost:${PORT}`);
22
29
  });
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "es6",
3
+ "target": "ES2021",
4
4
  "module": "commonjs",
5
+ "moduleResolution": "node",
5
6
  "rootDir": "./src",
6
7
  "outDir": "./dist",
8
+
7
9
  "esModuleInterop": true,
8
- "strict": true
9
- }
10
+ "strict": true,
11
+
12
+ "types": ["node"],
13
+ "resolveJsonModule": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "skipLibCheck": true,
16
+
17
+ "sourceMap": true
18
+ },
19
+ "include": ["src/**/*"]
10
20
  }
@@ -1,4 +1,4 @@
1
- // Auto-generated by create-backlist v5.0
1
+ // Auto-generated by create-backlist v5.1
2
2
  import swaggerUi from 'swagger-ui-express';
3
3
  import swaggerJsdoc from 'swagger-jsdoc';
4
4
  import { Express } from 'express';
@@ -17,17 +17,27 @@ const options: swaggerJsdoc.Options = {
17
17
  description: 'Development server',
18
18
  },
19
19
  ],
20
- // TODO: Add components (e.g., securitySchemes for JWT)
20
+ <% if (addAuth) { -%>
21
+ components: {
22
+ securitySchemes: {
23
+ bearerAuth: {
24
+ type: 'http',
25
+ scheme: 'bearer',
26
+ bearerFormat: 'JWT',
27
+ },
28
+ },
29
+ },
30
+ security: [{ bearerAuth: [] }],
31
+ <% } -%>
21
32
  },
22
- // Path to the API docs
23
- apis: ['./src/routes/*.ts', './src/routes.ts'], // Looks for JSDoc comments in routes
33
+
34
+ // Scan all TS files (routes/controllers) for JSDoc @swagger comments
35
+ apis: ['./src/**/*.ts'],
24
36
  };
25
37
 
26
38
  const swaggerSpec = swaggerJsdoc(options);
27
39
 
28
40
  export function setupSwagger(app: Express) {
29
41
  app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
30
- console.log(
31
- `📄 API documentation is available at http://localhost:<%= port %>/api-docs`
32
- );
42
+ console.log(`API documentation: http://localhost:<%= port %>/api-docs`);
33
43
  }