Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions capturecat-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ dependencies {
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
implementation 'org.flywaydb:flyway-core'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.capturecat.core.domain.user;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import com.capturecat.core.domain.BaseTimeEntity;
import com.capturecat.core.domain.tag.Tag;

Comment on lines 1 to 19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm BaseTimeEntity column names match DDL (created_date/last_modified_date).

If BaseTimeEntity uses camelCase without @column overrides, naming strategy must map to snake_case. Otherwise inserts will fail.

Run:


🏁 Script executed:

#!/bin/bash
# Verify BaseTimeEntity column mappings
fd -t f -a 'BaseTimeEntity.java' | xargs -I{} sh -c '
  echo "== {} ==";
  rg -n "createdDate|lastModifiedDate|@Column" -n {} -C2
'
# Check application naming strategy
rg -n "spring.jpa.hibernate.naming" -C2 **/application*.yml **/application*.properties || true

Length of output: 678


Explicitly define snake_case column names for audit fields
In BaseTimeEntity.java, annotate the fields with name overrides to match the DDL:

  • Line 21:
    @Column(name = "created_date", nullable = false, updatable = false)
  • Line 25:
    @Column(name = "last_modified_date", nullable = false)
🤖 Prompt for AI Agents
In capturecat-core/src/main/java/com/capturecat/core/domain/BaseTimeEntity.java
around lines 21 to 25, the audit timestamp fields must map to snake_case column
names; add @Column(name = "created_date", nullable = false, updatable = false)
to the createdDate field and @Column(name = "last_modified_date", nullable =
false) to the lastModifiedDate field, and ensure jakarta.persistence.Column is
imported so the JPA column name overrides compile.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserTag extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
private User user;

@ManyToOne(fetch = FetchType.LAZY)
private Tag tag;
}
Comment on lines 16 to 48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Map table/columns explicitly and enforce uniqueness at the JPA level.

Avoid reliance on naming strategies; add unique pair constraint and non-null FKs.

 package com.capturecat.core.domain.user;

 import jakarta.persistence.Entity;
 import jakarta.persistence.FetchType;
 import jakarta.persistence.GeneratedValue;
 import jakarta.persistence.GenerationType;
 import jakarta.persistence.Id;
 import jakarta.persistence.ManyToOne;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;

 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import lombok.Getter;

 import com.capturecat.core.domain.BaseTimeEntity;
 import com.capturecat.core.domain.tag.Tag;

 @Entity
 @NoArgsConstructor(access = AccessLevel.PROTECTED)
-public class UserTag extends BaseTimeEntity {
+@Table(name = "user_tag",
+    uniqueConstraints = @UniqueConstraint(name = "uk_user_tag_user_tag", columnNames = {"user_id", "tag_id"}))
+@Getter
+public class UserTag extends BaseTimeEntity {

 	@Id
 	@GeneratedValue(strategy = GenerationType.IDENTITY)
 	private Long id;

-	@ManyToOne(fetch = FetchType.LAZY)
-	private User user;
+	@ManyToOne(fetch = FetchType.LAZY, optional = false)
+	@JoinColumn(name = "user_id", nullable = false)
+	private User user;

-	@ManyToOne(fetch = FetchType.LAZY)
-	private Tag tag;
+	@ManyToOne(fetch = FetchType.LAZY, optional = false)
+	@JoinColumn(name = "tag_id", nullable = false)
+	private Tag tag;
 }

Optional: add ON DELETE CASCADE in SQL (see V2 comment) to auto-clean rows on user/tag deletion.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserTag extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
private Tag tag;
}
package com.capturecat.core.domain.user;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.Getter;
import com.capturecat.core.domain.BaseTimeEntity;
import com.capturecat.core.domain.tag.Tag;
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(
name = "user_tag",
uniqueConstraints = @UniqueConstraint(
name = "uk_user_tag_user_tag",
columnNames = {"user_id", "tag_id"}
)
)
@Getter
public class UserTag extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "tag_id", nullable = false)
private Tag tag;
}
🤖 Prompt for AI Agents
In capturecat-core/src/main/java/com/capturecat/core/domain/user/UserTag.java
around lines 16-29, the entity relies on implicit naming and lacks a JPA-level
uniqueness and non-null constraint for the user/tag relationship; update the
entity mapping to explicitly set the table and column names and enforce
uniqueness and non-null foreign keys by adding a @Table(name="user_tag",
uniqueConstraints=@UniqueConstraint(columnNames={"user_id","tag_id"})), annotate
both relations with @ManyToOne(fetch = FetchType.LAZY) and explicit
@JoinColumn(name="user_id", nullable=false,
foreignKey=@ForeignKey(name="fk_user_tag_user")), and @JoinColumn(name="tag_id",
nullable=false, foreignKey=@ForeignKey(name="fk_user_tag_tag")); if using
Hibernate and you want DB-level ON DELETE CASCADE behavior, add @OnDelete(action
= OnDeleteAction.CASCADE) on the associations or ensure your migration/DDL adds
ON DELETE CASCADE for these FKs.

8 changes: 8 additions & 0 deletions capturecat-core/src/main/resources/db/V2__create_user_tag.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
create table if not exists user_tag
(
id bigint generated by default as identity primary key,
user_id bigint constraint fk_user_tag_user references users
tag_id bigint constraint fk_user_tag_tag references tag,
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null,
);
Comment on lines 1 to
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Place the migration under Flyway’s default location.

V1 is under db/migration, but V2 is under db/. Flyway (Spring Boot default) scans classpath:db/migration; V2 will be skipped.

Move file to:

  • capturecat-core/src/main/resources/db/migration/V2__create_user_tag.sql
🤖 Prompt for AI Agents
capturecat-core/src/main/resources/db/V2__create_user_tag.sql lines 1-8: the
migration is placed in db/ instead of Flyway’s default classpath location
db/migration so Flyway will skip V2; move the file to
capturecat-core/src/main/resources/db/migration/V2__create_user_tag.sql (keeping
filename intact) so Spring Boot/Flyway will detect and run it.

⚠️ Potential issue

Fix SQL syntax and add constraints/indexes.

There’s a missing comma after user_id and a trailing comma before the closing parenthesis. Also add NOT NULL, unique pair, and helpful indexes.

-create table if not exists user_tag
-(
-    id                 bigint generated by default as identity primary key,
-    user_id            bigint constraint fk_user_tag_user references users
-    tag_id             bigint constraint fk_user_tag_tag references tag,
-    created_date       timestamp(6) not null,
-    last_modified_date timestamp(6) not null,
-);
+create table if not exists user_tag
+(
+    id                 bigint generated by default as identity primary key,
+    user_id            bigint not null constraint fk_user_tag_user references users on delete cascade,
+    tag_id             bigint not null constraint fk_user_tag_tag references tag on delete cascade,
+    created_date       timestamp(6) not null,
+    last_modified_date timestamp(6) not null,
+    constraint uk_user_tag_user_tag unique (user_id, tag_id)
+);
+
+create index if not exists idx_user_tag_user_id on user_tag (user_id);
+create index if not exists idx_user_tag_tag_id on user_tag (tag_id);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In capturecat-core/src/main/resources/db/V2__create_user_tag.sql around lines 1
to 8, fix the SQL syntax (add the missing comma after user_id and remove the
trailing comma before the closing parenthesis), mark user_id and tag_id NOT
NULL, add a UNIQUE constraint on (user_id, tag_id), explicitly declare foreign
key constraints referencing the correct parent tables, and add indexes for
user_id and tag_id to improve lookup performance.

87 changes: 87 additions & 0 deletions capturecat-core/src/main/resources/db/migration/V1__init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
create table if not exists users
(
id bigint not null primary key,
nickname varchar(50) not null,
email varchar(50) not null,
username varchar(50) not null unique,
password varchar(70),
role varchar(255) not null constraint users_role_check check (role in ('ADMIN', 'PREMIUM_USER', 'USER')),
tutorial_completed boolean not null,
provider varchar(255),
social_id varchar(255),
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null
);

create table if not exists user_social_account
(
id bigint not null primary key,
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null,
provider varchar(30) not null,
social_id varchar(100) not null,
unlink_key varchar(512),
user_id bigint not null constraint fk998rgv7jn090iyc77f8e1xsnq references users,
constraint uksj2lqxj8h0xuqf9v1dvtlkegt unique (provider, social_id)
);

create table if not exists refresh_token
(
id bigint not null primary key,
refresh_token_expiration bigint not null,
expiration varchar(255),
refresh_token varchar(255),
username varchar(255)
);
Comment on lines +28 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

refresh_token: ambiguous expirations and missing FK to user.

  • Two expiration columns conflict; keep one.
  • Add FK to users(username) to prevent orphans.
  • Consider identity PK.
-create table if not exists refresh_token
+create table if not exists refresh_token
 (
-    id                       bigint not null primary key,
-    refresh_token_expiration bigint not null,
-    expiration               varchar(255),
+    id                       bigint generated by default as identity primary key,
+    refresh_token_expiration bigint not null,
     refresh_token            varchar(255),
-    username                 varchar(255)
+    username                 varchar(50) not null,
+    constraint fk_refresh_token_user_username foreign key (username) references users(username) on delete cascade
 );

If you truly need two expirations, rename with clear semantics.

🤖 Prompt for AI Agents
In capturecat-core/src/main/resources/db/migration/V1__init.sql around lines
28–35, the refresh_token table currently has two ambiguous expiration columns
and lacks a foreign key to users; remove the redundant expiration varchar column
and keep a single clearly typed expiration (use refresh_token_expiration
bigint), change id to an identity/auto-increment column (e.g., BIGINT GENERATED
ALWAYS AS IDENTITY) instead of a plain bigint PK, ensure username is NOT NULL if
appropriate, and add an explicit foreign key constraint referencing
users(username) (optionally with ON DELETE CASCADE or RESTRICT based on desired
behavior); if you truly need two expirations, rename them to explicit semantic
names (e.g., issued_at_ts and expires_at_ts) instead of keeping generic
"expiration".


create table if not exists withdraw_log
(
id bigint generated by default as identity primary key,
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null,
image_cleanup_status varchar(255) not null constraint withdraw_log_image_cleanup_status_check check (image_cleanup_status in ('PENDING', 'DONE', 'FAILED')),
reason text,
user_id bigint not null
);
Comment on lines +37 to +45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

withdraw_log: add FK for user_id.

Currently no FK; risks orphaned rows.

 create table if not exists withdraw_log
 (
     id                   bigint generated by default as identity primary key,
     created_date         timestamp(6) not null,
     last_modified_date   timestamp(6) not null,
     image_cleanup_status varchar(255) not null constraint withdraw_log_image_cleanup_status_check check (image_cleanup_status in ('PENDING', 'DONE', 'FAILED')),
     reason               text,
-    user_id              bigint       not null
+    user_id              bigint       not null constraint fk_withdraw_log_user references users
 );

Optionally add on delete cascade depending on business rules.

🤖 Prompt for AI Agents
In capturecat-core/src/main/resources/db/migration/V1__init.sql around lines 37
to 45, the withdraw_log table lacks a foreign key on user_id which can lead to
orphaned rows; add a foreign key constraint like "constraint
withdraw_log_user_id_fkey foreign key (user_id) references users(id)" and decide
whether to append "on delete cascade" per business rules (or use "on delete
restrict" if cascading deletes are undesired), ensuring the constraint name and
referenced table/column match your schema.


create index if not exists idx_withdraw_log_user_id on withdraw_log (user_id);

create index if not exists idx_withdraw_log_created_date on withdraw_log (created_date);

create index if not exists idx_withdraw_log_cleanup_status on withdraw_log (image_cleanup_status, created_date);

create table if not exists images
(
id bigint generated by default as identity primary key,
size bigint,
file_name varchar(255),
file_url varchar(255),
capture_date date,
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null,
user_id bigint constraint fk13ljqfrfwbyvnsdhihwta8cpr references users
);

create table if not exists tag
(
id bigint generated by default as identity primary key,
name varchar(255),
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null
);

create table if not exists image_tag
(
id bigint generated by default as identity primary key,
image_id bigint constraint fk6q9wuvp5j846qtqod6xu3gma1 references images,
tag_id bigint constraint fk28yowgjl7oksr7dc0wj7f5il references tag,
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null
);
Comment on lines +73 to +80
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

image_tag: prevent duplicates and add indexes.

  • Add unique (image_id, tag_id).
  • Index both FKs for joins.
  • Make FKs NOT NULL.
 create table if not exists image_tag
 (
     id                 bigint generated by default as identity primary key,
-    image_id           bigint constraint fk6q9wuvp5j846qtqod6xu3gma1 references images,
-    tag_id             bigint constraint fk28yowgjl7oksr7dc0wj7f5il references tag,
+    image_id           bigint not null constraint fk6q9wuvp5j846qtqod6xu3gma1 references images,
+    tag_id             bigint not null constraint fk28yowgjl7oksr7dc0wj7f5il references tag,
     created_date       timestamp(6) not null,
     last_modified_date timestamp(6) not null
 );
+
+create unique index if not exists uk_image_tag_image_tag on image_tag (image_id, tag_id);
+create index if not exists idx_image_tag_image_id on image_tag (image_id);
+create index if not exists idx_image_tag_tag_id on image_tag (tag_id);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create table if not exists image_tag
(
id bigint generated by default as identity primary key,
image_id bigint constraint fk6q9wuvp5j846qtqod6xu3gma1 references images,
tag_id bigint constraint fk28yowgjl7oksr7dc0wj7f5il references tag,
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null
);
create table if not exists image_tag
(
id bigint generated by default as identity primary key,
image_id bigint not null constraint fk6q9wuvp5j846qtqod6xu3gma1 references images,
tag_id bigint not null constraint fk28yowgjl7oksr7dc0wj7f5il references tag,
created_date timestamp(6) not null,
last_modified_date timestamp(6) not null
);
create unique index if not exists uk_image_tag_image_tag on image_tag (image_id, tag_id);
create index if not exists idx_image_tag_image_id on image_tag (image_id);
create index if not exists idx_image_tag_tag_id on image_tag (tag_id);
🤖 Prompt for AI Agents
In capturecat-core/src/main/resources/db/migration/V1__init.sql around lines 73
to 80, modify the image_tag table definition to make image_id and tag_id NOT
NULL, add a unique constraint on (image_id, tag_id) to prevent duplicate
mappings, and add separate indexes on image_id and tag_id to speed joins;
implement this by altering the CREATE TABLE to declare image_id and tag_id as
NOT NULL, adding UNIQUE(image_id, tag_id) (or a named constraint), and adding
CREATE INDEX statements for image_id and tag_id after the table creation.


create table if not exists bookmark
(
id bigint generated by default as identity primary key,
image_id bigint constraint fkpowbsxsu0qwcon1yoxbsqkw4w references images,
user_id bigint constraint fko4vbqvq5trl11d85bqu5kl870 references users
);
Comment on lines +82 to +87
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

bookmark: prevent duplicates and add indexes.

Enforce one bookmark per (user, image) and speed lookups.

 create table if not exists bookmark
 (
     id       bigint generated by default as identity primary key,
     image_id bigint constraint fkpowbsxsu0qwcon1yoxbsqkw4w references images,
     user_id  bigint constraint fko4vbqvq5trl11d85bqu5kl870 references users
 );
+
+create unique index if not exists uk_bookmark_user_image on bookmark (user_id, image_id);
+create index if not exists idx_bookmark_image_id on bookmark (image_id);
+create index if not exists idx_bookmark_user_id on bookmark (user_id);
🤖 Prompt for AI Agents
In capturecat-core/src/main/resources/db/migration/V1__init.sql around lines 82
to 87, the bookmark table allows duplicate (user_id, image_id) pairs and lacks
indexes for fast lookups; add a UNIQUE constraint on (user_id, image_id) to
enforce one bookmark per user-image and create B-tree indexes on user_id and
image_id (or a composite index on (user_id, image_id) if preferred) to speed
queries; update the migration by altering the CREATE TABLE to include the
UNIQUE(user_id, image_id) clause and add CREATE INDEX statements for user_id and
image_id (or a single composite index) after the table creation, ensuring
constraint and index names follow project naming conventions.

3 changes: 3 additions & 0 deletions capturecat-core/src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ spring:
host: localhost
port: 6379

flyway:
enabled: false

image:
local:
base-path: ${user.home}/Desktop/capturecat/upload
Expand Down
Loading