-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 유저 태그 도메인 설계 및 유저 태그 생성 API #121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
328852d
479b3b6
f65b9d6
1702c6c
88eaad7
25891a8
f682e56
bbe6383
9eb0a3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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:
🤖 Prompt for AI AgentsFix 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);
🤖 Prompt for AI Agents |
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion refresh_token: ambiguous expirations and missing FK to user.
-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 |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion image_tag: prevent duplicates and add indexes.
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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
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:
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:
@Column(name = "created_date", nullable = false, updatable = false)@Column(name = "last_modified_date", nullable = false)🤖 Prompt for AI Agents