diff --git a/build.gradle b/build.gradle index f7031ea0..5a795e70 100644 --- a/build.gradle +++ b/build.gradle @@ -68,6 +68,22 @@ dependencies { // implementation 'software.amazon.awssdk:s3' implementation 'software.amazon.awssdk:s3:2.32.1' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + // QueryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" +} + +def querydslSrcDir = 'src/main/generated' + +clean { + delete file(querydslSrcDir) +} + +tasks.withType(JavaCompile).configureEach { + options.generatedSourceOutputDirectory = file(querydslSrcDir) } ext { diff --git a/docker-compose.yaml b/docker-compose.yaml index 4fd70c9c..04ed7088 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,11 +1,11 @@ services: - spring: - build: . - container_name: paletto - networks: - - web-net - ports: - - 8080:8080 +# spring: +# build: . +# container_name: paletto +# networks: +# - web-net +# ports: +# - 8080:8080 postgres: image: postgres:15 diff --git a/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignment.java b/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignment.java new file mode 100644 index 00000000..24b49190 --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignment.java @@ -0,0 +1,78 @@ +package hello.cluebackend.domain.assignment.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QAssignment is a Querydsl query type for Assignment + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QAssignment extends EntityPathBase { + + private static final long serialVersionUID = -473772911L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QAssignment assignment = new QAssignment("assignment"); + + public final QBaseEntity _super = new QBaseEntity(this); + + public final NumberPath assignmentId = createNumber("assignmentId", Long.class); + + public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom; + + public final StringPath content = createString("content"); + + //inherited + public final StringPath createdBy = _super.createdBy; + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final DateTimePath endDate = createDateTime("endDate", java.time.LocalDateTime.class); + + //inherited + public final DateTimePath lastModified = _super.lastModified; + + //inherited + public final StringPath lastModifiedBy = _super.lastModifiedBy; + + public final DateTimePath startDate = createDateTime("startDate", java.time.LocalDateTime.class); + + public final ListPath submissions = this.createList("submissions", hello.cluebackend.domain.submission.domain.Submission.class, hello.cluebackend.domain.submission.domain.QSubmission.class, PathInits.DIRECT2); + + public final StringPath title = createString("title"); + + public final hello.cluebackend.domain.user.domain.QUserEntity user; + + public QAssignment(String variable) { + this(Assignment.class, forVariable(variable), INITS); + } + + public QAssignment(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QAssignment(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QAssignment(PathMetadata metadata, PathInits inits) { + this(Assignment.class, metadata, inits); + } + + public QAssignment(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.classRoom = inits.isInitialized("classRoom") ? new hello.cluebackend.domain.classroom.domain.QClassRoom(forProperty("classRoom")) : null; + this.user = inits.isInitialized("user") ? new hello.cluebackend.domain.user.domain.QUserEntity(forProperty("user")) : null; + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignmentAttachment.java b/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignmentAttachment.java new file mode 100644 index 00000000..1f48ee7f --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/assignment/domain/QAssignmentAttachment.java @@ -0,0 +1,75 @@ +package hello.cluebackend.domain.assignment.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QAssignmentAttachment is a Querydsl query type for AssignmentAttachment + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QAssignmentAttachment extends EntityPathBase { + + private static final long serialVersionUID = 788938900L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QAssignmentAttachment assignmentAttachment = new QAssignmentAttachment("assignmentAttachment"); + + public final QBaseEntity _super = new QBaseEntity(this); + + public final QAssignment assignment; + + public final NumberPath assignmentAttachmentId = createNumber("assignmentAttachmentId", Long.class); + + public final StringPath contentType = createString("contentType"); + + //inherited + public final StringPath createdBy = _super.createdBy; + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + //inherited + public final DateTimePath lastModified = _super.lastModified; + + //inherited + public final StringPath lastModifiedBy = _super.lastModifiedBy; + + public final StringPath originalFileName = createString("originalFileName"); + + public final NumberPath size = createNumber("size", Long.class); + + public final EnumPath type = createEnum("type", FileType.class); + + public final StringPath value = createString("value"); + + public QAssignmentAttachment(String variable) { + this(AssignmentAttachment.class, forVariable(variable), INITS); + } + + public QAssignmentAttachment(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QAssignmentAttachment(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QAssignmentAttachment(PathMetadata metadata, PathInits inits) { + this(AssignmentAttachment.class, metadata, inits); + } + + public QAssignmentAttachment(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.assignment = inits.isInitialized("assignment") ? new QAssignment(forProperty("assignment"), inits.get("assignment")) : null; + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/assignment/domain/QBaseEntity.java b/src/main/generated/hello/cluebackend/domain/assignment/domain/QBaseEntity.java new file mode 100644 index 00000000..3562f97a --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/assignment/domain/QBaseEntity.java @@ -0,0 +1,43 @@ +package hello.cluebackend.domain.assignment.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QBaseEntity is a Querydsl query type for BaseEntity + */ +@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") +public class QBaseEntity extends EntityPathBase { + + private static final long serialVersionUID = 1570896856L; + + public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); + + public final StringPath createdBy = createString("createdBy"); + + public final DateTimePath createdDate = createDateTime("createdDate", java.time.LocalDateTime.class); + + public final DateTimePath lastModified = createDateTime("lastModified", java.time.LocalDateTime.class); + + public final StringPath lastModifiedBy = createString("lastModifiedBy"); + + public QBaseEntity(String variable) { + super(BaseEntity.class, forVariable(variable)); + } + + public QBaseEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QBaseEntity(PathMetadata metadata) { + super(BaseEntity.class, metadata); + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/classroom/domain/QClassRoom.java b/src/main/generated/hello/cluebackend/domain/classroom/domain/QClassRoom.java new file mode 100644 index 00000000..089e0e9c --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/classroom/domain/QClassRoom.java @@ -0,0 +1,58 @@ +package hello.cluebackend.domain.classroom.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QClassRoom is a Querydsl query type for ClassRoom + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QClassRoom extends EntityPathBase { + + private static final long serialVersionUID = 1743497663L; + + public static final QClassRoom classRoom = new QClassRoom("classRoom"); + + public final NumberPath classRoomId = createNumber("classRoomId", Long.class); + + public final ListPath classRoomUserList = this.createList("classRoomUserList", hello.cluebackend.domain.classroomuser.domain.ClassRoomUser.class, hello.cluebackend.domain.classroomuser.domain.QClassRoomUser.class, PathInits.DIRECT2); + + public final StringPath code = createString("code"); + + public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); + + public final StringPath description = createString("description"); + + public final ListPath directoryList = this.createList("directoryList", hello.cluebackend.domain.directory.domain.Directory.class, hello.cluebackend.domain.directory.domain.QDirectory.class, PathInits.DIRECT2); + + public final ListPath documentList = this.createList("documentList", hello.cluebackend.domain.document.domain.Document.class, hello.cluebackend.domain.document.domain.QDocument.class, PathInits.DIRECT2); + + public final BooleanPath isActivation = createBoolean("isActivation"); + + public final StringPath name = createString("name"); + + public final StringPath sort = createString("sort"); + + public final StringPath target = createString("target"); + + public QClassRoom(String variable) { + super(ClassRoom.class, forVariable(variable)); + } + + public QClassRoom(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QClassRoom(PathMetadata metadata) { + super(ClassRoom.class, metadata); + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/classroomuser/domain/QClassRoomUser.java b/src/main/generated/hello/cluebackend/domain/classroomuser/domain/QClassRoomUser.java new file mode 100644 index 00000000..4f209996 --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/classroomuser/domain/QClassRoomUser.java @@ -0,0 +1,54 @@ +package hello.cluebackend.domain.classroomuser.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QClassRoomUser is a Querydsl query type for ClassRoomUser + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QClassRoomUser extends EntityPathBase { + + private static final long serialVersionUID = 338153279L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QClassRoomUser classRoomUser = new QClassRoomUser("classRoomUser"); + + public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom; + + public final NumberPath classRoomUserId = createNumber("classRoomUserId", Long.class); + + public final hello.cluebackend.domain.user.domain.QUserEntity user; + + public QClassRoomUser(String variable) { + this(ClassRoomUser.class, forVariable(variable), INITS); + } + + public QClassRoomUser(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QClassRoomUser(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QClassRoomUser(PathMetadata metadata, PathInits inits) { + this(ClassRoomUser.class, metadata, inits); + } + + public QClassRoomUser(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.classRoom = inits.isInitialized("classRoom") ? new hello.cluebackend.domain.classroom.domain.QClassRoom(forProperty("classRoom")) : null; + this.user = inits.isInitialized("user") ? new hello.cluebackend.domain.user.domain.QUserEntity(forProperty("user")) : null; + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/directory/domain/QDirectory.java b/src/main/generated/hello/cluebackend/domain/directory/domain/QDirectory.java new file mode 100644 index 00000000..52046f5b --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/directory/domain/QDirectory.java @@ -0,0 +1,57 @@ +package hello.cluebackend.domain.directory.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QDirectory is a Querydsl query type for Directory + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QDirectory extends EntityPathBase { + + private static final long serialVersionUID = -1569116961L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QDirectory directory = new QDirectory("directory"); + + public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom; + + public final NumberPath directoryId = createNumber("directoryId", Long.class); + + public final NumberPath directoryOrder = createNumber("directoryOrder", Integer.class); + + public final ListPath documentList = this.createList("documentList", hello.cluebackend.domain.document.domain.Document.class, hello.cluebackend.domain.document.domain.QDocument.class, PathInits.DIRECT2); + + public final StringPath name = createString("name"); + + public QDirectory(String variable) { + this(Directory.class, forVariable(variable), INITS); + } + + public QDirectory(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QDirectory(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QDirectory(PathMetadata metadata, PathInits inits) { + this(Directory.class, metadata, inits); + } + + public QDirectory(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.classRoom = inits.isInitialized("classRoom") ? new hello.cluebackend.domain.classroom.domain.QClassRoom(forProperty("classRoom")) : null; + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/document/domain/QDocument.java b/src/main/generated/hello/cluebackend/domain/document/domain/QDocument.java new file mode 100644 index 00000000..031b742c --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/document/domain/QDocument.java @@ -0,0 +1,62 @@ +package hello.cluebackend.domain.document.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QDocument is a Querydsl query type for Document + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QDocument extends EntityPathBase { + + private static final long serialVersionUID = -687513363L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QDocument document = new QDocument("document"); + + public final hello.cluebackend.domain.classroom.domain.QClassRoom classRoom; + + public final StringPath content = createString("content"); + + public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); + + public final hello.cluebackend.domain.directory.domain.QDirectory directory; + + public final NumberPath documentId = createNumber("documentId", Long.class); + + public final StringPath title = createString("title"); + + public final NumberPath type = createNumber("type", Integer.class); + + public QDocument(String variable) { + this(Document.class, forVariable(variable), INITS); + } + + public QDocument(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QDocument(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QDocument(PathMetadata metadata, PathInits inits) { + this(Document.class, metadata, inits); + } + + public QDocument(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.classRoom = inits.isInitialized("classRoom") ? new hello.cluebackend.domain.classroom.domain.QClassRoom(forProperty("classRoom")) : null; + this.directory = inits.isInitialized("directory") ? new hello.cluebackend.domain.directory.domain.QDirectory(forProperty("directory"), inits.get("directory")) : null; + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/submission/domain/QBaseEntity.java b/src/main/generated/hello/cluebackend/domain/submission/domain/QBaseEntity.java new file mode 100644 index 00000000..ddd8f67c --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/submission/domain/QBaseEntity.java @@ -0,0 +1,43 @@ +package hello.cluebackend.domain.submission.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QBaseEntity is a Querydsl query type for BaseEntity + */ +@Generated("com.querydsl.codegen.DefaultSupertypeSerializer") +public class QBaseEntity extends EntityPathBase { + + private static final long serialVersionUID = -1972490601L; + + public static final QBaseEntity baseEntity = new QBaseEntity("baseEntity"); + + public final StringPath createdBy = createString("createdBy"); + + public final DateTimePath createdDate = createDateTime("createdDate", java.time.LocalDateTime.class); + + public final DateTimePath lastModified = createDateTime("lastModified", java.time.LocalDateTime.class); + + public final StringPath lastModifiedBy = createString("lastModifiedBy"); + + public QBaseEntity(String variable) { + super(BaseEntity.class, forVariable(variable)); + } + + public QBaseEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QBaseEntity(PathMetadata metadata) { + super(BaseEntity.class, metadata); + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmission.java b/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmission.java new file mode 100644 index 00000000..b1b55429 --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmission.java @@ -0,0 +1,72 @@ +package hello.cluebackend.domain.submission.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QSubmission is a Querydsl query type for Submission + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QSubmission extends EntityPathBase { + + private static final long serialVersionUID = 1469690831L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QSubmission submission = new QSubmission("submission"); + + public final QBaseEntity _super = new QBaseEntity(this); + + public final hello.cluebackend.domain.assignment.domain.QAssignment assignment; + + //inherited + public final StringPath createdBy = _super.createdBy; + + //inherited + public final DateTimePath createdDate = _super.createdDate; + + public final BooleanPath isSubmitted = createBoolean("isSubmitted"); + + //inherited + public final DateTimePath lastModified = _super.lastModified; + + //inherited + public final StringPath lastModifiedBy = _super.lastModifiedBy; + + public final NumberPath submissionId = createNumber("submissionId", Long.class); + + public final DateTimePath submittedAt = createDateTime("submittedAt", java.time.LocalDateTime.class); + + public final hello.cluebackend.domain.user.domain.QUserEntity user; + + public QSubmission(String variable) { + this(Submission.class, forVariable(variable), INITS); + } + + public QSubmission(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QSubmission(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QSubmission(PathMetadata metadata, PathInits inits) { + this(Submission.class, metadata, inits); + } + + public QSubmission(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.assignment = inits.isInitialized("assignment") ? new hello.cluebackend.domain.assignment.domain.QAssignment(forProperty("assignment"), inits.get("assignment")) : null; + this.user = inits.isInitialized("user") ? new hello.cluebackend.domain.user.domain.QUserEntity(forProperty("user")) : null; + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmissionAttachment.java b/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmissionAttachment.java new file mode 100644 index 00000000..6a185b7f --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/submission/domain/QSubmissionAttachment.java @@ -0,0 +1,64 @@ +package hello.cluebackend.domain.submission.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QSubmissionAttachment is a Querydsl query type for SubmissionAttachment + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QSubmissionAttachment extends EntityPathBase { + + private static final long serialVersionUID = -1140084142L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QSubmissionAttachment submissionAttachment = new QSubmissionAttachment("submissionAttachment"); + + public final StringPath contentType = createString("contentType"); + + public final StringPath originalFileName = createString("originalFileName"); + + public final NumberPath size = createNumber("size", Long.class); + + public final QSubmission submission; + + public final NumberPath SubmissionAttachmentId = createNumber("SubmissionAttachmentId", Long.class); + + public final EnumPath type = createEnum("type", fileType.class); + + public final hello.cluebackend.domain.user.domain.QUserEntity user; + + public final StringPath value = createString("value"); + + public QSubmissionAttachment(String variable) { + this(SubmissionAttachment.class, forVariable(variable), INITS); + } + + public QSubmissionAttachment(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QSubmissionAttachment(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QSubmissionAttachment(PathMetadata metadata, PathInits inits) { + this(SubmissionAttachment.class, metadata, inits); + } + + public QSubmissionAttachment(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.submission = inits.isInitialized("submission") ? new QSubmission(forProperty("submission"), inits.get("submission")) : null; + this.user = inits.isInitialized("user") ? new hello.cluebackend.domain.user.domain.QUserEntity(forProperty("user")) : null; + } + +} + diff --git a/src/main/generated/hello/cluebackend/domain/user/domain/QUserEntity.java b/src/main/generated/hello/cluebackend/domain/user/domain/QUserEntity.java new file mode 100644 index 00000000..0afcbca2 --- /dev/null +++ b/src/main/generated/hello/cluebackend/domain/user/domain/QUserEntity.java @@ -0,0 +1,47 @@ +package hello.cluebackend.domain.user.domain; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QUserEntity is a Querydsl query type for UserEntity + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QUserEntity extends EntityPathBase { + + private static final long serialVersionUID = -1925951728L; + + public static final QUserEntity userEntity = new QUserEntity("userEntity"); + + public final NumberPath classCode = createNumber("classCode", Integer.class); + + public final DateTimePath createdAt = createDateTime("createdAt", java.time.LocalDateTime.class); + + public final StringPath email = createString("email"); + + public final EnumPath role = createEnum("role", Role.class); + + public final NumberPath userId = createNumber("userId", Long.class); + + public final StringPath username = createString("username"); + + public QUserEntity(String variable) { + super(UserEntity.class, forVariable(variable)); + } + + public QUserEntity(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QUserEntity(PathMetadata metadata) { + super(UserEntity.class, metadata); + } + +} + diff --git a/src/main/java/hello/cluebackend/ClueBackendApplication.java b/src/main/java/hello/cluebackend/ClueBackendApplication.java index f309f9bd..e8f60d48 100644 --- a/src/main/java/hello/cluebackend/ClueBackendApplication.java +++ b/src/main/java/hello/cluebackend/ClueBackendApplication.java @@ -2,13 +2,22 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.data.domain.AuditorAware; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import java.util.Optional; +import java.util.UUID; + @SpringBootApplication @EnableJpaAuditing public class ClueBackendApplication { - public static void main(String[] args) { SpringApplication.run(ClueBackendApplication.class, args); } + + @Bean + public AuditorAware auditorProvider(){ + return () -> Optional.of(UUID.randomUUID().toString()); + } } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java b/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java new file mode 100644 index 00000000..17388aa4 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentCommandController.java @@ -0,0 +1,105 @@ +package hello.cluebackend.domain.assignment.api; + +import hello.cluebackend.domain.assignment.api.dto.response.*; +import hello.cluebackend.domain.assignment.application.AssignmentCommandService; +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; +import hello.cluebackend.domain.assignment.exception.AccessDeniedException; +import hello.cluebackend.domain.classroomuser.application.ClassroomUserService; +import hello.cluebackend.domain.submission.api.dto.response.SubmissionAttachmentDto; +import hello.cluebackend.domain.submission.application.SubmissionCommandService; +import hello.cluebackend.global.common.annotation.CurrentUser; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +@RestController +@RequestMapping("/api/assignments") +@RequiredArgsConstructor +@Slf4j + +// TODO : 권한 확인 애노테이션 추가 필요" + +public class AssignmentCommandController { + private final ClassroomUserService classroomUserService; + private final AssignmentCommandService assignmentCommandService; + private final SubmissionCommandService submissionCommandService; + + // 과제 단일 조회 + @GetMapping("/{assignmentId}") + public ResponseEntity getAssignment( + @CurrentUser Long userId, + @PathVariable Long assignmentId + ) { + AssignmentResponseDto result = assignmentCommandService.findById(assignmentId); + Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); + Long classroomId = assignment.getClassRoom().getClassRoomId(); + if (!classroomUserService.isUserInClassroom(classroomId, userId)) { + throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다."); + } + return ResponseEntity.ok(result); + } + + // 교실 과제 전체 조회 + @GetMapping("/{classId}/all") + public ResponseEntity> getAllClassroomAssignment( + @CurrentUser Long userId, + @PathVariable Long classId + ) { + List result = assignmentCommandService.findAllById(userId,classId); + + if (!classroomUserService.isUserInClassroom(classId, userId)) { + throw new AccessDeniedException("해당 수업실에 속하지 않은 유저입니다."); + } + + return ResponseEntity.ok(result); + } + + // 메인 페이지 모든 과제 조회 + @GetMapping("/me") + public ResponseEntity> getAllAssignments(@CurrentUser Long userId) { + List result = assignmentCommandService.findAllAssignmentMe(userId); + return ResponseEntity.ok(result); + } + + // 첨부 파일 혹은 링크 전체 조회 (선생, 학생) + @GetMapping("/{submissionId}/attachment") + public ResponseEntity> findAllAssignments(@CurrentUser Long userId, @PathVariable Long submissionId) { + List result = submissionCommandService.findAllAssignment(submissionId); + return ResponseEntity.ok(result); + } + + // 첨부 파일 다운로드 + @GetMapping("/{assignmentAttachmentId}/download") + public ResponseEntity assignmentAttachmentDownload( + @CurrentUser Long userId, + @PathVariable Long assignmentAttachmentId + ) throws IOException { + AssignmentAttachment assignmentAttachment = assignmentCommandService.findAssignmentAttachmentByIdOrderThrow(assignmentAttachmentId); + Resource resource = assignmentCommandService.downloadAttachment(assignmentAttachment); + + String original = assignmentAttachment.getOriginalFileName(); + String contentType = assignmentAttachment.getContentType(); + MediaType mediaType = (contentType != null) ? MediaType.parseMediaType(contentType) : MediaType.APPLICATION_OCTET_STREAM; + + return ResponseEntity.ok() + .contentType(mediaType) + .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment() + .filename(original, StandardCharsets.UTF_8) + .build() + .toString()) + .body(resource); + } +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java b/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java new file mode 100644 index 00000000..3f964350 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/AssignmentQueryController.java @@ -0,0 +1,96 @@ +package hello.cluebackend.domain.assignment.api; + +import hello.cluebackend.domain.assignment.api.dto.request.AssignmentAttachmentDto; +import hello.cluebackend.domain.assignment.api.dto.request.CreateAssignmentDto; +import hello.cluebackend.domain.assignment.api.dto.request.ModifyAssignmentDto; +import hello.cluebackend.domain.assignment.application.AssignmentQueryService; +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.submission.application.SubmissionQueryService; +import hello.cluebackend.global.common.annotation.CurrentUser; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@RestController +@RequestMapping("/api/assignments") +@RequiredArgsConstructor +@Slf4j +public class AssignmentQueryController { + private final AssignmentQueryService assignmentQueryService; + private final SubmissionQueryService submissionQueryService; + + // 과제 생성 + @PostMapping + public ResponseEntity createAssignment( + @CurrentUser Long userId, + @Valid @RequestBody CreateAssignmentDto request + ) { + Assignment assignment = assignmentQueryService.save(userId, request); + + submissionQueryService.assignToAllStudentsInClassroom(request.classId(), assignment); + return ResponseEntity.ok(assignment.getAssignmentId()); + } + + // 과제 삭제 + @DeleteMapping("/{assignmentId}") + public ResponseEntity deleteAssignment( + @CurrentUser Long userId, + @PathVariable Long assignmentId){ + assignmentQueryService.delete(assignmentId); + return ResponseEntity.ok("과제를 성공적으로 삭제했습니다."); + } + + // 과제 수정 + @PatchMapping("/{assignmentId}") + public ResponseEntity modifyAssignment( + @CurrentUser Long userId, + @PathVariable Long assignmentId, + @Valid @RequestBody ModifyAssignmentDto assignmentDto + ) { + Long assignment = assignmentQueryService.patchAssignment(assignmentId, assignmentDto); + + return ResponseEntity.ok(assignment); + } + + // 첨부 파일 추가 + @PostMapping("/{assignmentId}/file") + public ResponseEntity uploadAttachments( + @CurrentUser Long userId, + @PathVariable Long assignmentId, + @RequestParam("files") MultipartFile[] files + ) throws IOException { + + for (MultipartFile file : files) { + assignmentQueryService.uploadFileAttachment(assignmentId, file); + } + + return ResponseEntity.ok("과제 업로드가 성공했습니다."); + } + + // 첨부 링크 추가 + @PostMapping("/{assignmentId}/link") + public ResponseEntity urlAttachments( + @CurrentUser Long userId, + @PathVariable Long assignmentId, + @RequestBody List assignmentAttachmentDto + ){ + assignmentQueryService.uploadUrlAttachment(assignmentId, assignmentAttachmentDto); + return ResponseEntity.ok("과제 링크가 성공적으로 업로드 되었습니다."); + } + + // 첨부파일 혹은 링크 삭제 + @DeleteMapping("/attachment/{attachmentId}") + public ResponseEntity deleteAttachment( + @CurrentUser Long userId, + @PathVariable Long attachmentId + ) { + assignmentQueryService.deleteAttachment(attachmentId); + return ResponseEntity.ok("과제를 성공적으로 삭제했습니다."); + } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/AssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/AssignmentAttachmentDto.java new file mode 100644 index 00000000..3645818e --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/AssignmentAttachmentDto.java @@ -0,0 +1,5 @@ +package hello.cluebackend.domain.assignment.api.dto.request; + +public record AssignmentAttachmentDto( + String url +) {} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java new file mode 100644 index 00000000..bc9db012 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/CreateAssignmentDto.java @@ -0,0 +1,17 @@ +package hello.cluebackend.domain.assignment.api.dto.request; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +import java.time.LocalDateTime; + +@Builder +public record CreateAssignmentDto ( + @NotNull @JsonProperty("class_id") Long classId, + @NotNull String title, + @NotNull String content, + @NotNull @JsonProperty("start_date") @JsonFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime startDate, + @NotNull @JsonProperty("end_date") @JsonFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime endDate +){} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/ModifyAssignmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/ModifyAssignmentDto.java new file mode 100644 index 00000000..02a4695d --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/dto/request/ModifyAssignmentDto.java @@ -0,0 +1,18 @@ +package hello.cluebackend.domain.assignment.api.dto.request; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter @Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ModifyAssignmentDto { + private String title; + private String content; + private @JsonProperty("start_date") @JsonFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime startDate; + private @JsonProperty("end_date") @JsonFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime endDate; +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentAttachmentDto.java new file mode 100644 index 00000000..c7ce9506 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentAttachmentDto.java @@ -0,0 +1,13 @@ +package hello.cluebackend.domain.assignment.api.dto.response; + +import hello.cluebackend.domain.assignment.domain.FileType; +import lombok.Builder; + +@Builder +public record AssignmentAttachmentDto( + FileType type, + String value, + String originalFileName, + String contentType, + Long size +) {} diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java new file mode 100644 index 00000000..0a19e7ad --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/AssignmentResponseDto.java @@ -0,0 +1,19 @@ +package hello.cluebackend.domain.assignment.api.dto.response; + +import lombok.Builder; + +import java.time.LocalDateTime; +import java.util.List; + +@Builder +public record AssignmentResponseDto( + Long assignmentId, + String title, + String content, + LocalDateTime startDate, + LocalDateTime endDate, + String userName, + + // 과제 첨부 데이터 + List xAssignmentResponseDtos +) { } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java new file mode 100644 index 00000000..9f583958 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/GetAllAssignmentDto.java @@ -0,0 +1,23 @@ +package hello.cluebackend.domain.assignment.api.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Builder +public class GetAllAssignmentDto { + private Long assignmentId; // 과제 아이디 + private String title; // 과제 제목 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime startDate; // 과제 시작일 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime endDate; // 마감일 + + public GetAllAssignmentDto(Long assignmentId, String title, LocalDateTime startDate, LocalDateTime endDate){ + this.assignmentId = assignmentId; + this.title = title; + this.startDate = startDate; + this.endDate = endDate; + } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java new file mode 100644 index 00000000..16d912b6 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/api/dto/response/SubmissionCheck.java @@ -0,0 +1,16 @@ +package hello.cluebackend.domain.assignment.api.dto.response; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Builder +public class SubmissionCheck { + private String userName; + private int classNumberGrade; + private Long submissionId; + private boolean isSubmitted; + private LocalDateTime submittedAt; +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java b/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java new file mode 100644 index 00000000..d38bd7dc --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentCommandService.java @@ -0,0 +1,95 @@ +package hello.cluebackend.domain.assignment.application; + +import hello.cluebackend.domain.assignment.api.dto.response.AssignmentAttachmentDto; +import hello.cluebackend.domain.assignment.api.dto.response.AssignmentResponseDto; +import hello.cluebackend.domain.assignment.api.dto.response.GetAllAssignmentDto; +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; +import hello.cluebackend.domain.assignment.persistence.AssignmentRepository; +import hello.cluebackend.domain.assignment.persistence.AssignmentAttachmentRepository; +import hello.cluebackend.domain.classroom.service.ClassRoomService; +import hello.cluebackend.domain.classroom.domain.ClassRoom; +import hello.cluebackend.domain.file.service.FileService; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AssignmentCommandService{ + private final AssignmentRepository assignmentRepository; + private final ClassRoomService classRoomService; + private final AssignmentAttachmentRepository assignmentAttachmentRepository; + private final FileService fileService; + + // 과제 단일 조회 + public AssignmentResponseDto findById(Long assignmentId) { + Assignment a = findByIdOrThrow(assignmentId); + List assignmentAttachments = assignmentAttachmentRepository.findAllByAssignment(a); + + List assignmentAttachmentDtos = assignmentAttachments.stream() + .map(aa -> AssignmentAttachmentDto.builder() + .type(aa.getType()) + .value(aa.getValue()) + .originalFileName(aa.getOriginalFileName()) + .contentType(aa.getContentType()) + .size(aa.getSize()) + .build()) + .toList(); + + return AssignmentResponseDto.builder() + .assignmentId(a.getAssignmentId()) + .title(a.getTitle()) + .content(a.getContent()) + .startDate(a.getStartDate()) + .endDate(a.getEndDate()) + .userName(a.getUser().getUsername()) + .xAssignmentResponseDtos(assignmentAttachmentDtos) + .build(); + } + + // 과제 전체 조회 + public List findAllById(Long userId, Long classId) { + ClassRoom classRoom = classRoomService.findById(classId).toEntity(); + List assignments = assignmentRepository.findAllByClassRoom(classRoom); + return assignments.stream() + .map(a -> findById(a.getAssignmentId())) + .toList(); + } + + // 과제 ID를 통한 조회 + public Assignment findByIdOrThrow(Long assignmentId) { + return assignmentRepository.findById(assignmentId) + .orElseThrow(() -> new EntityNotFoundException("해당 과제를 찾을수 없습니다.")); + } + + public AssignmentAttachment findAssignmentAttachmentByIdOrderThrow(Long attachmentId){ + return assignmentAttachmentRepository.findById(attachmentId) + .orElseThrow(() -> new EntityNotFoundException("해당 첨부 파일을 찾을수 없습니다.")); + } + + // 사용자가 속한 모든 수업 과제 조회 + public List findAllAssignmentMe(Long userId) { + List assignments = assignmentRepository.getAllByUser(userId); + return assignments.stream() + .map(a -> GetAllAssignmentDto.builder() + .assignmentId(a.getAssignmentId()) + .title(a.getTitle()) + .startDate(a.getStartDate()) + .endDate(a.getEndDate()) + .build() + ) + .toList(); + } + + public Resource downloadAttachment(AssignmentAttachment assignmentAttachment) throws IOException { + String path = assignmentAttachment.getValue(); + return fileService.downloadFile(path); + } +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java b/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java new file mode 100644 index 00000000..7c766617 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/application/AssignmentQueryService.java @@ -0,0 +1,115 @@ +package hello.cluebackend.domain.assignment.application; + +import hello.cluebackend.domain.assignment.api.dto.request.AssignmentAttachmentDto; +import hello.cluebackend.domain.assignment.api.dto.request.CreateAssignmentDto; +import hello.cluebackend.domain.assignment.api.dto.request.ModifyAssignmentDto; +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; +import hello.cluebackend.domain.assignment.domain.FileType; +import hello.cluebackend.domain.assignment.persistence.AssignmentRepository; +import hello.cluebackend.domain.assignment.persistence.AssignmentAttachmentRepository; +import hello.cluebackend.domain.classroom.domain.ClassRoom; +import hello.cluebackend.domain.classroom.domain.repository.ClassRoomRepository; +import hello.cluebackend.domain.file.service.FileService; +import hello.cluebackend.domain.user.domain.UserEntity; +import hello.cluebackend.domain.user.service.UserService; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AssignmentQueryService{ + private final AssignmentRepository assignmentRepository; + private final ClassRoomRepository classRoomRepository; + private final AssignmentCommandService assignmentCommandService; + private final AssignmentAttachmentRepository assignmentAttachmentRepository; + private final FileService fileService; + private final UserService userService; + + // 과제 생성 + @Transactional + public Assignment save(Long userId, CreateAssignmentDto request) { + UserEntity user = userService.findById(userId).toEntity(); + + ClassRoom classRoom = classRoomRepository.findById(request.classId()) + .orElseThrow(() -> new EntityNotFoundException("해당 교실을 찾을수 없습니다")); + + Assignment assignment = Assignment.builder() + .classRoom(classRoom) + .user(user) + .title(request.title()) + .content(request.content()) + .startDate(request.startDate()) + .endDate(request.endDate()) + .build(); + + assignmentRepository.save(assignment); + return assignment; + } + + // 과제 삭제 + @Transactional + public void delete(Long assignmentId) { + Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); + assignmentRepository.delete(assignment); + } + + // 과제 수정 + @Transactional + public Long patchAssignment(Long assignmentId, ModifyAssignmentDto dto){ + Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); + assignment.patch(dto.getTitle(), dto.getContent(), dto.getStartDate(), dto.getEndDate()); + return assignment.getAssignmentId(); + } + + // url 업로드 + @Transactional + public void uploadUrlAttachment(Long assignmentId, List dtos) { + Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); + + List result = dtos.stream() + .map(dto -> AssignmentAttachment.builder() + .assignment(assignment) + .type(FileType.URL) + .value(dto.url()) + .build()) + .toList(); + + assignmentAttachmentRepository.saveAll(result); + } + + // 첨부 파일 추가 + public void uploadFileAttachment(Long assignmentId, MultipartFile file) { + Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); + + String storedFileName = fileService.storeFile(file); + + AssignmentAttachment result = AssignmentAttachment.builder() + .assignment(assignment) + .type(FileType.FILE) + .value(storedFileName) + .originalFileName(file.getOriginalFilename()) + .contentType(file.getContentType()) + .size(file.getSize()) + .build(); + assignmentAttachmentRepository.save(result); + } + + // 첨부 파일 업로드 삭제 + @Transactional + public void deleteAttachment(Long attachmentId) { + AssignmentAttachment assignmentAttachment = assignmentCommandService.findAssignmentAttachmentByIdOrderThrow(attachmentId); + if(assignmentAttachment.getType() == FileType.FILE){ + fileService.deleteFile(assignmentAttachment.getValue()); + } + assignmentAttachmentRepository.delete(assignmentAttachment); + } + + + +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java b/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java index 6907ded3..4d5fe99f 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/Assignment.java @@ -1,40 +1,73 @@ package hello.cluebackend.domain.assignment.domain; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import hello.cluebackend.domain.submission.domain.Submission; import hello.cluebackend.domain.classroom.domain.ClassRoom; import hello.cluebackend.domain.user.domain.UserEntity; import jakarta.persistence.*; import lombok.*; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity -@Table(name = "assignments") -@Getter @Setter -@AllArgsConstructor -@NoArgsConstructor +@Table(name = "assignment") +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder -public class Assignment { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) +@AllArgsConstructor +public class Assignment extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "assignment_id") private Long assignmentId; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "class_room_id") + @JoinColumn(name = "classroom_id") private ClassRoom classRoom; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "creator_id") + @JoinColumn(name = "user_id") private UserEntity user; - @Column(name = "title") + @Column(nullable = false) private String title; - @Column(name = "content") + @Column(columnDefinition = "TEXT") private String content; - @Column(name = "start_date") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime startDate; - @Column(name = "due_date") - private LocalDateTime dueDate; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + private LocalDateTime endDate; + + + @OneToMany(mappedBy = "assignment", cascade = CascadeType.REMOVE, orphanRemoval = true) + @Builder.Default + @JsonIgnore + private List submissions = new ArrayList<>(); + + // DTO 기반 정적 팩토리 메서드 + public static Assignment create(ClassRoom classRoom, UserEntity user, String title, String content, LocalDateTime startDate, LocalDateTime endDate) { + return Assignment.builder() + .classRoom(classRoom) + .user(user) + .title(title) + .content(content) + .startDate(startDate) + .endDate(endDate) + .build(); + } + + // patch 메서드 (null 체크 후 업데이트) + public void patch(String title, String content, LocalDateTime startDate, LocalDateTime endDate) { + if (title != null) this.title = title; + if (content != null) this.content = content; + if (startDate != null) this.startDate = startDate; + if (endDate != null) this.endDate = endDate; + } } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java index 10bfb82b..229f9058 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentAttachment.java @@ -1,60 +1,34 @@ package hello.cluebackend.domain.assignment.domain; -import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto; -import hello.cluebackend.domain.user.domain.UserEntity; import jakarta.persistence.*; import lombok.*; -import java.time.LocalDateTime; - @Entity @Table(name = "assignment_attachment") @Getter @Setter -@AllArgsConstructor -@NoArgsConstructor @Builder -public class AssignmentAttachment { +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AssignmentAttachment extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "assignment_attachment_id") private Long assignmentAttachmentId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="assignment_id") private Assignment assignment; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private UserEntity user; - - @Column(name = "original_file_name") - private String originalFileName; - - @Column(name = "stored_file_name") - private String storedFileName; - - @Column(name = "file_path") - private String filePath; - - @Column(name = "file_size") - private Integer fileSize; - + // FILE, URL + @Column(nullable = false) @Enumerated(EnumType.STRING) - @Column(name = "submit_type") - private SubmitType submitType; + private FileType type; - @Column(name = "update_date") - private LocalDateTime updateDate; + // 실제 파일이면 S3 Key, URL이면 링크 + @Column(nullable = false) + private String value; - public AssignmentAttachmentDto toDto() { - return AssignmentAttachmentDto.builder() - .assignmentAttachmentId(assignmentAttachmentId) - .assignment(assignment) - .user(user) - .originalFileName(originalFileName) - .storedFileName(storedFileName) - .filePath(filePath) - .fileSize(fileSize) - .submitType(submitType) - .updateDate(updateDate) - .build(); - } + // 파일일 경우 메타데이터 + private String originalFileName; + private String contentType; + private Long size; } diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java deleted file mode 100644 index b83ee612..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentCheck.java +++ /dev/null @@ -1,32 +0,0 @@ -package hello.cluebackend.domain.assignment.domain; - -import hello.cluebackend.domain.user.domain.UserEntity; -import jakarta.persistence.*; -import lombok.*; - -import java.time.LocalDateTime; - -@Entity -@Table(name="assignment_check") -@Getter @Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class AssignmentCheck { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long assignmentCheckId; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private UserEntity user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "assignment_id") - private Assignment assignment; - - @Column(name="is_submitted") - private Boolean isSubmitted; - - @Column(name="submitted_at") - private LocalDateTime submittedAt; -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentContent.java b/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentContent.java deleted file mode 100644 index e85b0357..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/AssignmentContent.java +++ /dev/null @@ -1,46 +0,0 @@ -package hello.cluebackend.domain.assignment.domain; - -import hello.cluebackend.domain.user.domain.UserEntity; -import jakarta.persistence.*; -import lombok.*; - -import java.time.LocalDateTime; - -@Entity -@Table(name = "assignment_content") -@Getter @Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class AssignmentContent { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name="assignment_content_id") - private Long assignmentContentId; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="user_id") - private UserEntity user; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="assignment_id") - private Assignment assignment; - - @JoinColumn(name="original_file_name") - private String originalFileName; - - @Column(name="stored_file_name") - private String storedFileName; - - @Column(name="file_path") - private String filePath; - - @Column(name="file_size") - private int fileSize; - - @Enumerated(EnumType.STRING) - @Column(name = "submit_type") - private SubmitType submitType; // 1. 랑크 , 2. 파일 - - @Column(name="update_date") - private LocalDateTime updateDate; -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java b/src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java new file mode 100644 index 00000000..1beba01c --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/BaseEntity.java @@ -0,0 +1,32 @@ +package hello.cluebackend.domain.assignment.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@EntityListeners(AuditingEntityListener.class) +@MappedSuperclass +@Getter +public class BaseEntity { + @CreatedDate + @Column + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime lastModified; + + @CreatedBy + @Column(updatable = false) + private String createdBy; + + @LastModifiedBy + private String lastModifiedBy; +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java b/src/main/java/hello/cluebackend/domain/assignment/domain/FileType.java similarity index 60% rename from src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java rename to src/main/java/hello/cluebackend/domain/assignment/domain/FileType.java index 285d5015..69114d45 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/SubmitType.java +++ b/src/main/java/hello/cluebackend/domain/assignment/domain/FileType.java @@ -1,7 +1,6 @@ package hello.cluebackend.domain.assignment.domain; -public enum SubmitType { +public enum FileType { FILE, - LINK, - IMAGE + URL } diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentCheckRepository.java b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentCheckRepository.java deleted file mode 100644 index f6a74981..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentCheckRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -package hello.cluebackend.domain.assignment.domain.repository; - -import hello.cluebackend.domain.assignment.domain.Assignment; -import hello.cluebackend.domain.assignment.domain.AssignmentCheck; -import io.lettuce.core.dynamic.annotation.Param; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface AssignmentCheckRepository extends JpaRepository { - @Query(""" -SELECT ac.assignment -FROM AssignmentCheck ac -JOIN ac.assignment a -JOIN a.classRoom cr -JOIN cr.classRoomUserList cru -WHERE ac.user.userId = :userId - AND ac.isSubmitted = false -""") - List findUnsubmittedAssignmentsByUserId(@Param("userId") Long userId); -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentContentRepository.java b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentContentRepository.java deleted file mode 100644 index 74711fc7..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentContentRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package hello.cluebackend.domain.assignment.domain.repository; - -import hello.cluebackend.domain.assignment.domain.AssignmentContent; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AssignmentContentRepository extends JpaRepository { -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentRepository.java b/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentRepository.java deleted file mode 100644 index 90da6c70..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package hello.cluebackend.domain.assignment.domain.repository; - -import hello.cluebackend.domain.assignment.domain.Assignment; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface AssignmentRepository extends JpaRepository { - List findAllByClassRoom_ClassRoomId(Long classId); -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/exception/AccessDeniedException.java b/src/main/java/hello/cluebackend/domain/assignment/exception/AccessDeniedException.java new file mode 100644 index 00000000..69e927eb --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/exception/AccessDeniedException.java @@ -0,0 +1,11 @@ +package hello.cluebackend.domain.assignment.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.FORBIDDEN) +public class AccessDeniedException extends RuntimeException { + public AccessDeniedException(String message) { + super(message); + } +} diff --git a/src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java b/src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java deleted file mode 100644 index 48ed0c42..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/exception/AssignmentNotFoundException.java +++ /dev/null @@ -1,15 +0,0 @@ -package hello.cluebackend.domain.assignment.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.NOT_FOUND) // 404 Not Found 응답 -public class AssignmentNotFoundException extends RuntimeException { - public AssignmentNotFoundException() { - super("해당 과제를 찾을 수 없습니다."); - } - - public AssignmentNotFoundException(String message) { - super(message); - } -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java b/src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java deleted file mode 100644 index eb04b19a..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/exception/UnauthorizedException.java +++ /dev/null @@ -1,15 +0,0 @@ -package hello.cluebackend.domain.assignment.exception; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ResponseStatus; - -@ResponseStatus(HttpStatus.FORBIDDEN) // 403 Forbidden 응답 -public class UnauthorizedException extends RuntimeException { - public UnauthorizedException() { - super("접근 권한이 없습니다."); - } - - public UnauthorizedException(String message) { - super(message); - } -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java b/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentAttachmentRepository.java similarity index 87% rename from src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java rename to src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentAttachmentRepository.java index 507ef691..215bdc57 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/domain/repository/AssignmentAttachmentRepository.java +++ b/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentAttachmentRepository.java @@ -1,4 +1,4 @@ -package hello.cluebackend.domain.assignment.domain.repository; +package hello.cluebackend.domain.assignment.persistence; import hello.cluebackend.domain.assignment.domain.Assignment; import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; @@ -10,4 +10,4 @@ @Repository public interface AssignmentAttachmentRepository extends JpaRepository { List findAllByAssignment(Assignment assignment); -} +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java b/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java new file mode 100644 index 00000000..930c3e58 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/assignment/persistence/AssignmentRepository.java @@ -0,0 +1,21 @@ +package hello.cluebackend.domain.assignment.persistence; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.classroom.domain.ClassRoom; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface AssignmentRepository extends JpaRepository { + @Query("SELECT a FROM Submission s" + + " join s.assignment a" + + " where s.user.userId =:userId" + + " AND s.isSubmitted = false") + List getAllByUser(@Param("userId") Long userId); + + List findAllByClassRoom(ClassRoom classRoom); +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java deleted file mode 100644 index c06e14e4..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/presentation/AssignmentController.java +++ /dev/null @@ -1,168 +0,0 @@ -package hello.cluebackend.domain.assignment.presentation; - - -import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto; -import hello.cluebackend.domain.assignment.presentation.dto.request.AssignmentCreateRequestDto; -import hello.cluebackend.domain.assignment.presentation.dto.response.GetAssignmentResponseDto; -import hello.cluebackend.domain.assignment.presentation.dto.response.StudentAssignmentRemain; -import hello.cluebackend.domain.assignment.service.AssignmentService; -import hello.cluebackend.domain.assignment.service.FileService; -import hello.cluebackend.global.config.JWTUtil; -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.util.UriUtils; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -@Slf4j -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/assignments") -public class AssignmentController { - private final AssignmentService assignmentService; - private final JWTUtil jWTUtil; - private final FileService fileService; - - private Long jwtTokenTaker(HttpServletRequest request){ - String token = jWTUtil.getToken(request); - Long userId = jWTUtil.getUserId(token); - return userId; - } - - // 교실 전체 과제 조회하기 - @GetMapping("/{classId}") - public ResponseEntity> getAllAssignment( - HttpServletRequest request, - @PathVariable Long classId - ){ - Long userId = jwtTokenTaker(request); - return ResponseEntity.ok(assignmentService.getAllAssignment(classId, userId)); - } - - // 과제 생성하기 - @PostMapping("/{classId}") - public ResponseEntity createAssignment( - HttpServletRequest request, - @PathVariable Long classId, - @RequestPart("metadata") AssignmentCreateRequestDto requestDto, - @RequestPart("files") List files - ){ - try { - Long userId = jwtTokenTaker(request); - assignmentService.createAssignment(userId, classId, requestDto, files); - return ResponseEntity.status(HttpStatus.CREATED).body("과제 생성 완료"); - } catch(Exception e) { - log.error(e.getMessage()); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); - } - } - - // 선생님 첨부 파일 다운 받기 - @GetMapping("/attachment/{attachmentId}") - public ResponseEntity downloadAttachment( - @PathVariable Long attachmentId, - HttpServletRequest request - ){ - Long userId = jwtTokenTaker(request); - - try { - Resource file = fileService.downloadFile(attachmentId, userId); - AssignmentAttachmentDto assignmentDto = assignmentService.findById(attachmentId); - String fullPath = assignmentDto.getFilePath(); - UrlResource resource = new UrlResource("file:" + fullPath); - - String encodedFileName = UriUtils.encode(fullPath.split("_")[1], StandardCharsets.UTF_8); - String contentDisposition = "attachment; filename=\"" + encodedFileName + "\""; - - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition) - .body(resource); - } catch(Exception e) { - log.error(e.getMessage()); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); - } - } -// // 선생님 첨부 파일 다운 받기 -// @GetMapping("/attachment/{attachmentId}") -// public ResponseEntity downloadAttachment( -// @PathVariable Long attachmentId, -// HttpServletRequest request -// ){ -// Long userId = jwtTokenTaker(request); -// -// try { -// Resource file = fileService.downloadFile(attachmentId, userId); -// -// return ResponseEntity.ok() -// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"downloaded-file\"") -// .contentType(MediaType.APPLICATION_OCTET_STREAM) -// .body(file); -// } catch(Exception e) { -// log.error(e.getMessage()); -// return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); -// } -// } - - - // 학생 메인 페이지 과제 남은 일수 - @GetMapping("/check") - public ResponseEntity> getStudentRemainCard ( - HttpServletRequest request - ){ - Long userId = jwtTokenTaker(request); - List remains = assignmentService.getUnsubmittedAssignments(userId); - return ResponseEntity.ok(remains); - } - - - -// -// @DeleteMapping("/{classId}") -// public ResponseEntity deleteAssignment( -// HttpServletRequest request, -// @PathVariable Long classId, -// @RequestParam("assignmentId") Long assignmentId -// ) { -// String token = jWTUtil.getToken(request); -// Long userId = jWTUtil.getUserId(token); -// -// try { -// assignmentService.deleteAssignment(classId, assignmentId, userId); -// return ResponseEntity.noContent().build(); -// } catch (AssignmentNotFoundException e) { -// return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); -// } catch (UnauthorizedException e) { -// return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null); -// } -// } -// -// @GetMapping("/{classId}") -// public ResponseEntity getAssignmentList( -// HttpServletRequest request, -// @PathVariable Long classId -// ) { -// String token = jWTUtil.getToken(request); -// Long userId = jWTUtil.getUserId(token); -// -// return ResponseEntity.ok(assignmentService.getAssignmentList(classId)); -// } - - -// @GetMapping("{classId}") -// public ResponseEntity> getAllAssignments( -// @PathVariable Long classId, -// @RequestParam Long userId -// ){ -// return ResponseEntity.ok(assignmentService.getAssignmentsForStudent(classId, userId)); -// } -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/AssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/AssignmentAttachmentDto.java deleted file mode 100644 index 0114b0d6..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/AssignmentAttachmentDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package hello.cluebackend.domain.assignment.presentation.dto; - -import hello.cluebackend.domain.assignment.domain.Assignment; -import hello.cluebackend.domain.assignment.domain.SubmitType; -import hello.cluebackend.domain.user.domain.UserEntity; -import lombok.*; - -import java.time.LocalDateTime; - -@Getter -@Setter -@ToString -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class AssignmentAttachmentDto { - private Long assignmentAttachmentId; - private Assignment assignment; - private UserEntity user; - private String originalFileName; - private String storedFileName; - private String filePath; - private Integer fileSize; - private SubmitType submitType; - private LocalDateTime updateDate; -} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentCreateRequestDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentCreateRequestDto.java deleted file mode 100644 index 9441d343..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentCreateRequestDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package hello.cluebackend.domain.assignment.presentation.dto.request; - -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDateTime; - -@Getter @Setter -public class AssignmentCreateRequestDto { - private String title; - private String content; - private LocalDateTime startData; - private LocalDateTime dueDate; -} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentSubmitStatusDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentSubmitStatusDto.java deleted file mode 100644 index 71c2b38a..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/request/AssignmentSubmitStatusDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package hello.cluebackend.domain.assignment.presentation.dto.request; - -import java.time.LocalDateTime; - -public record AssignmentSubmitStatusDto( - String userName, - boolean isSubmitted, - LocalDateTime submittedAt, - Long userId, - Long assignmentId -) { -} diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentDuration.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentDuration.java deleted file mode 100644 index c8ccd5db..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/AssignmentDuration.java +++ /dev/null @@ -1,8 +0,0 @@ -package hello.cluebackend.domain.assignment.presentation.dto.response; - -import java.time.LocalDateTime; - -public record AssignmentDuration( - LocalDateTime startDate, - LocalDateTime endDate -){} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java deleted file mode 100644 index 214c782e..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/Assignmentfile.java +++ /dev/null @@ -1,7 +0,0 @@ -package hello.cluebackend.domain.assignment.presentation.dto.response; - -public record Assignmentfile( - Long fileId, - String fileName, - int fileSize -) {} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAssignmentResponseDto.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAssignmentResponseDto.java deleted file mode 100644 index f417079b..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/GetAssignmentResponseDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package hello.cluebackend.domain.assignment.presentation.dto.response; - -import java.time.LocalDateTime; -import java.util.List; - -public record GetAssignmentResponseDto( - Long assignmentId, - String title, - LocalDateTime endDate, - String duringDate, - List files -) {} diff --git a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java b/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java deleted file mode 100644 index ce105dc1..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/presentation/dto/response/StudentAssignmentRemain.java +++ /dev/null @@ -1,7 +0,0 @@ -package hello.cluebackend.domain.assignment.presentation.dto.response; - -public record StudentAssignmentRemain( - String title, - String duration, - Long assignmentId -) {} diff --git a/src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java b/src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java deleted file mode 100644 index 09ff87b3..00000000 --- a/src/main/java/hello/cluebackend/domain/assignment/service/AssignmentService.java +++ /dev/null @@ -1,347 +0,0 @@ -package hello.cluebackend.domain.assignment.service; - -import com.zaxxer.hikari.pool.HikariProxyCallableStatement; -import hello.cluebackend.domain.assignment.domain.Assignment; -import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; -import hello.cluebackend.domain.assignment.domain.AssignmentCheck; -import hello.cluebackend.domain.assignment.domain.SubmitType; -import hello.cluebackend.domain.assignment.domain.repository.AssignmentAttachmentRepository; -import hello.cluebackend.domain.assignment.domain.repository.AssignmentContentRepository; -import hello.cluebackend.domain.assignment.domain.repository.AssignmentRepository; -import hello.cluebackend.domain.assignment.domain.repository.AssignmentCheckRepository; -import hello.cluebackend.domain.assignment.presentation.dto.AssignmentAttachmentDto; -import hello.cluebackend.domain.assignment.presentation.dto.request.AssignmentCreateRequestDto; -import hello.cluebackend.domain.assignment.presentation.dto.response.AssignmentDuration; -import hello.cluebackend.domain.assignment.presentation.dto.response.Assignmentfile; -import hello.cluebackend.domain.assignment.presentation.dto.response.GetAssignmentResponseDto; -import hello.cluebackend.domain.assignment.presentation.dto.response.StudentAssignmentRemain; -import hello.cluebackend.domain.classroom.domain.ClassRoom; -import hello.cluebackend.domain.classroom.domain.repository.ClassRoomRepository; -import hello.cluebackend.domain.classroomuser.domain.ClassRoomUser; -import hello.cluebackend.domain.classroomuser.domain.repository.ClassRoomUserRepository; -import hello.cluebackend.domain.document.presentation.dto.FileUpload; -import hello.cluebackend.domain.user.domain.Role; -import hello.cluebackend.domain.user.domain.UserEntity; -import hello.cluebackend.domain.user.domain.repository.UserRepository; -import jakarta.persistence.EntityNotFoundException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -import java.io.File; -import java.io.IOException; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -@Slf4j -@Service -@RequiredArgsConstructor -public class AssignmentService { - - @Value("${upload.local.dir}") - private String uploadDir; - - private final UserRepository userRepository; - private final ClassRoomUserRepository classRoomUserRepository; - private final AssignmentRepository assignmentRepository; - private final AssignmentAttachmentRepository assignmentAttachmentRepository; - private final AssignmentCheckRepository assignmentCheckRepository; - private final ClassRoomRepository classRoomRepository; - - private final FileService fileService; - - - private UserEntity validated(Long userId, Long classId){ - UserEntity user = userRepository.findById(userId) - .orElseThrow(() -> new EntityNotFoundException("해당 유저를 찾을수 없습니다.")); -// System.out.println("user = " + user); - if(!Role.TEACHER.equals(user.getRole())) { - throw new AccessDeniedException("권한이 부족한 사용자 입니다."); - } - - List classRoomUsers = classRoomUserRepository.findByUser_UserId(userId); - - boolean isMemberOfClass = classRoomUsers.stream() - .anyMatch(classRoomUser -> classRoomUser.getClassRoom().getClassRoomId().equals(classId)); - - if (!isMemberOfClass) { - throw new AccessDeniedException("해당 수업에 속하지 않은 사용자입니다."); - } - return user; - } - - private String calculateRemainingTime(LocalDateTime now, LocalDateTime dueDate) { - Duration duration = Duration.between(now, dueDate); - long days = duration.toDays(); - long hours = duration.minusDays(days).toHours(); - - if (duration.isNegative()) { - return "마감됨"; - } - - return days + "일 " + hours + "시간 남음"; - } - - public String generateUniqueFileName(String originalFileName) { - String extension = ""; - - int dotIndex = originalFileName.lastIndexOf("."); - if (dotIndex > 0) { - extension = originalFileName.substring(dotIndex); // 확장자 포함 - } - - String uuid = UUID.randomUUID().toString(); - - return uuid + extension; - } - - public List getAllAssignment(Long classId, Long userId) { - - UserEntity user = validated(classId, userId); - - List assignments = assignmentRepository.findAllByClassRoom_ClassRoomId(classId); - - return assignments.stream().map(assignment -> { - List attachments = assignmentAttachmentRepository.findAllByAssignment(assignment); - - List fileDtos = attachments.stream() - .map(attachment -> new Assignmentfile( - attachment.getAssignmentAttachmentId(), - attachment.getOriginalFileName(), - attachment.getFileSize() - )) - .toList(); - - String remainingTime = calculateRemainingTime(LocalDateTime.now(), assignment.getDueDate()); - - return new GetAssignmentResponseDto( - assignment.getAssignmentId(), - assignment.getTitle(), - assignment.getDueDate(), - remainingTime, - fileDtos - ); - }).toList(); - } - - @Transactional - public void createAssignment(Long userId, Long classId, AssignmentCreateRequestDto requestDto, List files) { - UserEntity user = validated(userId, classId); - ClassRoom classRoom = classRoomRepository.findById(classId) - .orElseThrow(() -> new EntityNotFoundException("해당 반을 찾을 수 없습니다.")); - - Assignment assignment = Assignment.builder() - .classRoom(classRoom) - .user(user) - .title(requestDto.getTitle()) - .content(requestDto.getContent()) - .startDate(requestDto.getStartData()) - .dueDate(requestDto.getDueDate()) - .build(); - - assignmentRepository.save(assignment); - - if (files != null && !files.isEmpty()) { - for (MultipartFile file : files) { -// String storedFileName = fileService.storeFile(file); - FileUpload uploadResult = upload(file); - AssignmentAttachment attachment = AssignmentAttachment.builder() - .assignment(assignment) - .user(user) - .originalFileName(file.getOriginalFilename()) - .storedFileName(uploadResult.getStoredFileName()) - .filePath(uploadResult.getFullPath()) - .fileSize((int) file.getSize()) - .submitType(SubmitType.FILE) - .updateDate(LocalDateTime.now()) - .build(); - - assignmentAttachmentRepository.save(attachment); - } - } - -// @Transactional -// public void createAssignment(Long userId, Long classId, AssignmentCreateRequestDto requestDto, List files) { -// UserEntity user = validated(userId, classId); -// ClassRoom classRoom = classRoomRepository.findById(classId) -// .orElseThrow(() -> new EntityNotFoundException("해당 반을 찾을 수 없습니다.")); -// -// Assignment assignment = Assignment.builder() -// .classRoom(classRoom) -// .user(user) -// .title(requestDto.getTitle()) -// .content(requestDto.getContent()) -// .startDate(requestDto.getStartData()) -// .dueDate(requestDto.getDueDate()) -// .build(); -// -// assignmentRepository.save(assignment); -// -// if (files != null && !files.isEmpty()) { -// for (MultipartFile file : files) { -// String storedFileName = fileService.storeFile(file); -// AssignmentAttachment attachment = AssignmentAttachment.builder() -// .assignment(assignment) -// .user(user) -// .originalFileName(file.getOriginalFilename()) -// .storedFileName(storedFileName) -//// .filePath("/uploads/" + storedFileName) -// .filePath(storedFileName) -// .fileSize((int) file.getSize()) -// .submitType(SubmitType.FILE) -// .updateDate(LocalDateTime.now()) -// .build(); -// -// assignmentAttachmentRepository.save(attachment); -// } -// } - - List students = classRoomUserRepository.findAllStudentsByClassRoomId(classId); - for (UserEntity student : students) { - AssignmentCheck check = AssignmentCheck.builder() - .assignment(assignment) - .user(student) - .isSubmitted(false) - .submittedAt(null) - .build(); - assignmentCheckRepository.save(check); - } - } - - public List getUnsubmittedAssignments(Long userId) { - UserEntity user = userRepository.findById(userId) - .orElseThrow(() -> new EntityNotFoundException("해당 학생을 찾을수 없습니다.")); - - List assignments = assignmentCheckRepository.findUnsubmittedAssignmentsByUserId(userId); - - return assignments.stream() - .map(a -> new StudentAssignmentRemain( - a.getTitle(), - new AssignmentDuration(a.getStartDate(), a.getDueDate()).toString(), - a.getAssignmentId() - )).collect(Collectors.toList()); - } - - public FileUpload upload(MultipartFile file) { - - String originalFileName = file.getOriginalFilename(); - String storedFileName = generateStoredFileName(originalFileName); - String fullPath = getFullPath(storedFileName); - File dest = new File(fullPath); - - if(!dest.getParentFile().exists()) { - boolean created = dest.getParentFile().mkdirs(); - if(!created) { - log.error("Unable to create directory {}", dest.getParentFile().getAbsolutePath()); - throw new RuntimeException("Directory creation failed"); - } - } - - try { - file.transferTo(dest); - } catch (IOException e) { - log.error("File uploading failed {}", originalFileName, e); - throw new RuntimeException("File uploading failed " + originalFileName, e); - } - log.info("File uploaded {}", originalFileName); - return FileUpload.builder() - .originalFileName(originalFileName) - .storedFileName(storedFileName) - .fullPath(fullPath) - .build(); - } - - // uuid_원본파일명 - private String generateStoredFileName(String originalFileName) { - return UUID.randomUUID().toString() + "_" + originalFileName; - } - - public String getFullPath(String fileName) { - return uploadDir + File.separator + fileName; - } - - public AssignmentAttachmentDto findById(Long attachmentId) { - AssignmentAttachment findAssignmentAttachment = assignmentAttachmentRepository.findById(attachmentId).orElseThrow(() -> new EntityNotFoundException("해당 과제가 없습니다.")); - return findAssignmentAttachment.toDto(); - } - -// public void createAssignment(Long userId, Long classId, AssignmentCreateRequestDto requestDto, MultipartFile file) { -// UserEntity user = userRepository.findById(userId) -// .orElseThrow(() -> new EntityNotFoundException("해당 사용자를 찾을 수 없습니다.")); -// -// if (user.getRole() != Role.TEACHER) { -// throw new AccessDeniedException("과제를 생성할 권한이 없습니다."); -// } -// -// Assignment assignment = Assignment.builder() -// .creator(user) -// .classRoom(ClassRoom.builder().classRoomId(classId).build()) -// .title(requestDto.getTitle()) -// .content(requestDto.getContent()) -// .startDate(requestDto.getStartData()) -// .dueDate(requestDto.getDueDate()) -// .build(); -// -// assignmentRepository.save(assignment); -// -// // 파일이 있는 경우에만 첨부 정보 저장 -// if (file != null && !file.isEmpty()) { -// // 실제 파일 저장 처리 (ex: S3, 로컬, etc) -// String storedFileName = generateUniqueFileName(file.getOriginalFilename()); -// String filePath = "TeacherAssignment/" + storedFileName; -// -// // 예: S3Uploader.upload(file, storedFileName) -// AssignmentAttachment attachment = AssignmentAttachment.builder() -// .user(user) -// .assignment(assignment) -// .originalFileName(file.getOriginalFilename()) -// .storedFileName(storedFileName) -// .filePath(filePath) -// .fileSize((int) file.getSize()) -// .submitType(2) // 예시: 2 = 일반 업로드, 1 = 링크 저장 -// .updateDate(LocalDateTime.now()) -// .build(); -// -// assignmentAttachmentRepository.save(attachment); -// } -// } - -// public void deleteAssignment(Long userId, Long assignmentId, Long classRoomId) { -// UserEntity user = userRepository.findById(userId) -// .orElseThrow(() -> new EntityNotFoundException("해당 사용자를 찾을 수 없습니다.")); -// -// if (user.getRole() != Role.TEACHER) { -// throw new UnauthorizedException("삭제 권한이 없습니다."); -// } -// -// Assignment assignment = assignmentRepository.findById(assignmentId) -// .orElseThrow(() -> new EntityNotFoundException("해당 과제를 찾을 수 없습니다.")); -// -// if (!assignment.getClassRoom().getClassRoomId().equals(classRoomId)) { -// throw new IllegalArgumentException("해당 과제는 이 반에 속하지 않습니다."); -// } -// -// assignmentRepository.delete(assignment); -// } -// -// -// -// public Object getSubmissionStatus(Long classId, Long assignmentId) { -// List checks = assignmentCheckRepository.findAllByAssignment_AssignmentId(assignmentId); -// -// return checks.stream().map(check -> new AssignmentSubmitStatusDto( -// check.getUser().getUsername(), -// check.getIsSubmitted(), -// check.getSubmittedAt(), -// check.getUser().getUserId(), -// check.getAssignment().getAssignmentId() -// )).toList(); -// } -} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/classroom/domain/repository/ClassRoomRepository.java b/src/main/java/hello/cluebackend/domain/classroom/domain/repository/ClassRoomRepository.java index 64c5b206..a7d42636 100644 --- a/src/main/java/hello/cluebackend/domain/classroom/domain/repository/ClassRoomRepository.java +++ b/src/main/java/hello/cluebackend/domain/classroom/domain/repository/ClassRoomRepository.java @@ -12,4 +12,6 @@ public interface ClassRoomRepository extends JpaRepository { Boolean existsByCode(String code); Optional findByCode(String code); + + ClassRoom findByClassRoomId(Long classRoomId); } diff --git a/src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java b/src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java index a2d8b445..d397ff4d 100644 --- a/src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java +++ b/src/main/java/hello/cluebackend/domain/classroom/presentation/ClassRoomController.java @@ -1,14 +1,14 @@ package hello.cluebackend.domain.classroom.presentation; +import hello.cluebackend.domain.assignment.application.AssignmentCommandService; import hello.cluebackend.domain.classroom.presentation.dto.ClassRoomCardDto; import hello.cluebackend.domain.classroom.presentation.dto.ClassRoomDto; import hello.cluebackend.domain.classroom.service.ClassRoomService; import hello.cluebackend.domain.user.domain.Role; -import hello.cluebackend.domain.user.presentation.dto.UserDto; -import hello.cluebackend.domain.user.service.UserService; import hello.cluebackend.global.config.JWTUtil; import jakarta.persistence.EntityNotFoundException; import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -20,15 +20,11 @@ @Slf4j @RestController @RequestMapping("/api/class") +@RequiredArgsConstructor public class ClassRoomController { private final JWTUtil jwtUtil; private final ClassRoomService classRoomService; - public ClassRoomController(JWTUtil jwtUtil, ClassRoomService classRoomService) { - this.jwtUtil = jwtUtil; - this.classRoomService = classRoomService; - } - @GetMapping public ResponseEntity> getAllClassRooms(HttpServletRequest request){ String token = jwtUtil.getToken(request); diff --git a/src/main/java/hello/cluebackend/domain/classroomuser/application/ClassroomUserService.java b/src/main/java/hello/cluebackend/domain/classroomuser/application/ClassroomUserService.java new file mode 100644 index 00000000..477f8e6d --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/classroomuser/application/ClassroomUserService.java @@ -0,0 +1,37 @@ +package hello.cluebackend.domain.classroomuser.application; + +import hello.cluebackend.domain.classroom.domain.ClassRoom; +import hello.cluebackend.domain.classroom.service.ClassRoomService; +import hello.cluebackend.domain.classroomuser.domain.ClassRoomUser; +import hello.cluebackend.domain.classroomuser.domain.repository.ClassRoomUserRepository; +import hello.cluebackend.domain.user.domain.UserEntity; +import hello.cluebackend.domain.user.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ClassroomUserService { + private final ClassRoomUserRepository classRoomUserRepository; + private final UserService userService; + private final ClassRoomService classRoomService; + + // 해당 교실에 속한 모든 사용자 조회 + public List findAllClassroomUser(ClassRoom classRoom){ + List classRoomUsers = classRoomUserRepository.findAllByClassRoom(classRoom); + List users = classRoomUsers.stream() + .map(cru -> cru.getUser()) + .toList(); + return users; + } + + // 수업실에 해당 유저가 속하는지 확인하는 로직 + public boolean isUserInClassroom(Long classRoomId, Long userId) { + ClassRoom classRoom = classRoomService.findById(classRoomId).toEntity(); + UserEntity user = userService.findById(userId).toEntity(); + + return classRoomUserRepository.existsByClassRoomAndUser(classRoom,user); + } +} diff --git a/src/main/java/hello/cluebackend/domain/classroomuser/domain/ClassRoomUser.java b/src/main/java/hello/cluebackend/domain/classroomuser/domain/ClassRoomUser.java index d9f779a3..d9ba340f 100644 --- a/src/main/java/hello/cluebackend/domain/classroomuser/domain/ClassRoomUser.java +++ b/src/main/java/hello/cluebackend/domain/classroomuser/domain/ClassRoomUser.java @@ -8,7 +8,6 @@ @Entity @Table(name="class_room_user") @Getter -@Setter @Builder @NoArgsConstructor @AllArgsConstructor diff --git a/src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java b/src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java index 124c3f23..204be1a2 100644 --- a/src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java +++ b/src/main/java/hello/cluebackend/domain/classroomuser/domain/repository/ClassRoomUserRepository.java @@ -21,8 +21,12 @@ public interface ClassRoomUserRepository extends JpaRepository findUsersByClassRoomId(@Param("classId") Long classId); - @Query("SELECT cru.user FROM ClassRoomUser cru WHERE cru.classRoom.classRoomId = :classRoomId") + @Query("SELECT cru.user FROM ClassRoomUser cru WHERE cru.classRoom.classRoomId = :classRoomId AND cru.user.role = hello.cluebackend.domain.user.domain.Role.STUDENT") List findAllStudentsByClassRoomId(Long classRoomId); List user(UserEntity user); + + List findAllByClassRoom(ClassRoom classRoom); + + boolean existsByClassRoomAndUser(ClassRoom classRoom, UserEntity user); } \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/file/exception/S3FileNotFoundException.java b/src/main/java/hello/cluebackend/domain/file/exception/S3FileNotFoundException.java new file mode 100644 index 00000000..93dd1389 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/file/exception/S3FileNotFoundException.java @@ -0,0 +1,7 @@ +package hello.cluebackend.domain.file.exception; + +import com.amazonaws.services.s3.model.AmazonS3Exception; + +public class S3FileNotFoundException extends AmazonS3Exception { + public S3FileNotFoundException(String message) { super(message); } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/assignment/service/FileService.java b/src/main/java/hello/cluebackend/domain/file/service/FileService.java similarity index 65% rename from src/main/java/hello/cluebackend/domain/assignment/service/FileService.java rename to src/main/java/hello/cluebackend/domain/file/service/FileService.java index 040088ee..7feb5348 100644 --- a/src/main/java/hello/cluebackend/domain/assignment/service/FileService.java +++ b/src/main/java/hello/cluebackend/domain/file/service/FileService.java @@ -1,11 +1,10 @@ -package hello.cluebackend.domain.assignment.service; +package hello.cluebackend.domain.file.service; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.S3Object; -import hello.cluebackend.domain.assignment.domain.AssignmentAttachment; -import hello.cluebackend.domain.assignment.domain.repository.AssignmentAttachmentRepository; +import hello.cluebackend.domain.file.exception.S3FileNotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.InputStreamResource; @@ -20,11 +19,11 @@ @RequiredArgsConstructor public class FileService { private final AmazonS3Client amazonS3Client; - private final AssignmentAttachmentRepository assignmentAttachmentRepository; @Value("${cloud.aws.s3.bucket}") private String bucket; + // 파일 추가 public String storeFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); String extension = ""; @@ -39,22 +38,28 @@ public String storeFile(MultipartFile file) { try { amazonS3Client.putObject(new PutObjectRequest(bucket, storedFileName, file.getInputStream(), metadata)); // 공개 권한 설정 - -// return amazonS3Client.getUrl(bucket, storedFileName).toString(); return storedFileName; } catch (IOException e) { throw new RuntimeException("파일 업로드에 실패했습니다.", e); } } - public Resource downloadFile(Long attachmentId, Long userId) { - AssignmentAttachment attachment = assignmentAttachmentRepository.findById(attachmentId) - .orElseThrow(() -> new RuntimeException("파일을 찾을 수 없습니다.")); - - String filePath = attachment.getFilePath(); - - S3Object s3Object = amazonS3Client.getObject(bucket, filePath); + // 파일 삭제 + public void deleteFile(String storedFileName) { + try{ + amazonS3Client.deleteObject(bucket, storedFileName); + }catch(Exception e){ + throw new RuntimeException("파일 삭제에 실패했습니다."); + } + } - return new InputStreamResource(s3Object.getObjectContent()); + // 파일 다운로드 + public Resource downloadFile(String filePath) throws IOException { + try{ + S3Object s3Object = amazonS3Client.getObject(bucket, filePath); + return new InputStreamResource(s3Object.getObjectContent()); + } catch (Exception e) { + throw new S3FileNotFoundException("해당 파일 경로를 찾을수 없습니다: " + filePath); + } } } diff --git a/src/main/java/hello/cluebackend/domain/submission/api/SubmissionCommandController.java b/src/main/java/hello/cluebackend/domain/submission/api/SubmissionCommandController.java new file mode 100644 index 00000000..c8e234f6 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/api/SubmissionCommandController.java @@ -0,0 +1,73 @@ +package hello.cluebackend.domain.submission.api; + +import hello.cluebackend.domain.assignment.api.dto.response.SubmissionCheck; +import hello.cluebackend.domain.submission.api.dto.response.SubmissionDto; +import hello.cluebackend.domain.submission.application.SubmissionCommandService; +import hello.cluebackend.domain.submission.domain.SubmissionAttachment; +import hello.cluebackend.global.common.annotation.CurrentUser; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; + +@RestController +@RequestMapping("/api/submissions") +@RequiredArgsConstructor +@Slf4j +public class SubmissionCommandController { + private final SubmissionCommandService submissionCommandService; + + // 할당된 과제 전체 조회 + @GetMapping("/assignment/{assignmentId}") + public ResponseEntity> findAllSubmission( + @CurrentUser Long userId, + @PathVariable Long assignmentId + ) { + List result = submissionCommandService.findAllByAssignmentId(userId,assignmentId); + return ResponseEntity.ok(result); + } + + // 할당된 과제 단일 조회 + @GetMapping("/{submissionId}") + public ResponseEntity findSubmission( + @CurrentUser Long userId, + @PathVariable Long assignmentId + ) { + SubmissionDto result = submissionCommandService.findByAssignmentId(assignmentId); + + return ResponseEntity.ok(result); + } + + // 전체 학생 과제 제출 여부 + @GetMapping("/{assignmentId}/check") + public ResponseEntity> checkAssignment( + @CurrentUser Long userId, + @PathVariable Long assignmentId + ){ + List assignmentChecks = submissionCommandService.checkAssignment(userId,assignmentId); + return ResponseEntity.ok(assignmentChecks); + } + + // 첨부파일 다운로드 + @GetMapping("/{submissionAttachmentId}/download") + public ResponseEntity submissionAttachmentDownload( + @CurrentUser Long userId, + @PathVariable Long submissionAttachmentId + ) throws IOException { + SubmissionAttachment submissionAttachment = submissionCommandService.findsubmissionAttachmentByIdOrThrow(submissionAttachmentId); + Resource resource = submissionCommandService.downloadAttachment(submissionAttachment); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + submissionAttachment.getOriginalFileName() + submissionAttachment.getContentType() + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(resource); + } +} diff --git a/src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java b/src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java new file mode 100644 index 00000000..a0312adf --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/api/SubmissionQueryController.java @@ -0,0 +1,74 @@ +package hello.cluebackend.domain.submission.api; + +import hello.cluebackend.domain.submission.api.dto.request.SubmissionAttachmentUrlDto; +import hello.cluebackend.domain.submission.application.SubmissionQueryService; +import hello.cluebackend.domain.submission.domain.Submission; +import hello.cluebackend.domain.submission.domain.SubmissionAttachment; +import hello.cluebackend.global.common.annotation.CurrentUser; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@RestController +@RequestMapping("/api/submissions") +@RequiredArgsConstructor +@Slf4j +public class SubmissionQueryController { + private final SubmissionQueryService submissionQueryService; + + // 과제 제출 하기 + @PatchMapping("/{submissionId}/submit") + public ResponseEntity submitSubmission( + @CurrentUser Long userId, + @PathVariable Long submissionId + ) { + Submission submission = submissionQueryService.submitSubmission(submissionId); + return ResponseEntity.ok(submission); + } + + // 과제 제출 취소하기 + @PatchMapping("/{submissionId}/cancel") + public ResponseEntity cancelSubmission( + @CurrentUser Long userId, + @PathVariable Long submissionId + ){ + Submission submission = submissionQueryService.cancelSubmission(submissionId); + return ResponseEntity.ok(submission); + } + + + @DeleteMapping("/{submissionAttachmentId}") + public ResponseEntity deleteSubmission( + @CurrentUser Long userId, + @PathVariable Long submissionAttachmentId + ){ + submissionQueryService.deleteSubmissionAttachment(submissionAttachmentId); + return ResponseEntity.ok("과제 첨부파일이 성공적으로 삭제되었습니다."); + } + + // 과제 제출 첨부 파일 추가 + @PostMapping("/{submissionId}/file") + public ResponseEntity fileUpload( + @CurrentUser Long userId, + @PathVariable Long submissionId, + @RequestBody MultipartFile file + ) throws IOException { + SubmissionAttachment submissionAttachment = submissionQueryService.fileUpload(submissionId, file); + return ResponseEntity.ok(submissionAttachment); + } + + // 과제 제출 첨부 링크 추가 + @PostMapping("/{submisisonId}/link") + public ResponseEntity deleteFile( + @CurrentUser Long userId, + @PathVariable Long submissionId, + @RequestBody SubmissionAttachmentUrlDto dto + ) { + submissionQueryService.linkUpload(submissionId, dto); + return ResponseEntity.ok("첨부 파일 삭제"); + } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAssignmentAttachmentDto.java b/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAssignmentAttachmentDto.java new file mode 100644 index 00000000..998b45a0 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAssignmentAttachmentDto.java @@ -0,0 +1,4 @@ +package hello.cluebackend.domain.submission.api.dto.request; + +public record SubmissionAssignmentAttachmentDto() { +} diff --git a/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAttachmentUrlDto.java b/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAttachmentUrlDto.java new file mode 100644 index 00000000..3b1fd081 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/api/dto/request/SubmissionAttachmentUrlDto.java @@ -0,0 +1,5 @@ +package hello.cluebackend.domain.submission.api.dto.request; + +public record SubmissionAttachmentUrlDto( + String url +) { } diff --git a/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionAttachmentDto.java b/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionAttachmentDto.java new file mode 100644 index 00000000..c1300e25 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionAttachmentDto.java @@ -0,0 +1,13 @@ +package hello.cluebackend.domain.submission.api.dto.response; + +import hello.cluebackend.domain.submission.domain.fileType; +import lombok.Builder; + +@Builder +public record SubmissionAttachmentDto( + fileType type, + String value, + String originalFileName, + String contentType, + Long size +) { } diff --git a/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionDto.java b/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionDto.java new file mode 100644 index 00000000..345e3001 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/api/dto/response/SubmissionDto.java @@ -0,0 +1,22 @@ +package hello.cluebackend.domain.submission.api.dto.response; + +import lombok.*; + +import java.time.LocalDateTime; + +@AllArgsConstructor +@Data +@Builder +public class SubmissionDto { + // 과제 데이터 + private String title; + private LocalDateTime startDate; + private LocalDateTime endDate; + + // 유저 정보 + private String userName; + + // 제출 정보 + private boolean isSubmitted; + private LocalDateTime submittedAt; +} diff --git a/src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java b/src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java new file mode 100644 index 00000000..6d6ee5c9 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/application/SubmissionCommandService.java @@ -0,0 +1,115 @@ +package hello.cluebackend.domain.submission.application; + +import hello.cluebackend.domain.assignment.api.dto.response.SubmissionCheck; +import hello.cluebackend.domain.assignment.application.AssignmentCommandService; +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.assignment.persistence.AssignmentRepository; +import hello.cluebackend.domain.file.service.FileService; +import hello.cluebackend.domain.submission.api.dto.response.SubmissionAttachmentDto; +import hello.cluebackend.domain.submission.api.dto.response.SubmissionDto; +import hello.cluebackend.domain.submission.domain.Submission; +import hello.cluebackend.domain.submission.domain.SubmissionAttachment; +import hello.cluebackend.domain.submission.persistence.SubmissionRepository; +import hello.cluebackend.domain.submission.persistence.SubmissionAttachmentRepository; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class SubmissionCommandService { + private final SubmissionRepository submissionRepository; + private final SubmissionAttachmentRepository submissionAttachmentRepository; + private final AssignmentRepository assignmentRepository; + private final AssignmentCommandService assignmentCommandService; + private final FileService fileService; + + // 과제 제출 여부 확인 API + public List checkAssignment(Long userId, Long assignmentId) { + Assignment assignment = assignmentRepository.findById(assignmentId) + .orElseThrow(() -> new EntityNotFoundException("해당 과제를 찾을수 없습니다.")); + List submissions = submissionRepository.findAllByAssignment(assignment); + return submissions.stream() + .filter(s -> s.getUser().getUserId().equals(userId)) + .map(s -> SubmissionCheck.builder() + .submissionId(s.getSubmissionId()) + .userName(s.getUser().getUsername()) + .classNumberGrade(s.getUser().getClassCode()) + .isSubmitted(s.getIsSubmitted()) + .submittedAt(s.getSubmittedAt()) + .build() + ) + .toList(); + } + + public List findAllByAssignmentId(Long userId, Long assignmentId) { + Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); + List submissions = submissionRepository.findAllByAssignment(assignment); + + return submissions.stream() + .map(s -> SubmissionDto.builder() + .title(s.getAssignment().getTitle()) + .startDate(s.getAssignment().getStartDate()) + .endDate(s.getAssignment().getEndDate()) + .userName(s.getUser().getUsername()) + .isSubmitted(s.getIsSubmitted()) + .submittedAt(s.getSubmittedAt()) + .build() + ) + .toList(); + } + + // + public SubmissionDto findByAssignmentId(Long assignmentId) { + Assignment assignment = assignmentCommandService.findByIdOrThrow(assignmentId); + + Submission s = submissionRepository.findByAssignment(assignment); + + return SubmissionDto.builder() + .title(s.getAssignment().getTitle()) + .startDate(s.getAssignment().getStartDate()) + .endDate(s.getAssignment().getEndDate()) + .userName(s.getUser().getUsername()) + .isSubmitted(s.getIsSubmitted()) + .submittedAt(s.getSubmittedAt()) + .build(); + } + + public Submission findByIdOrThrow(Long submissionId) { + return submissionRepository.findById(submissionId) + .orElseThrow(() -> new EntityNotFoundException("해당 제출 과제를 찾을수 없습니다.")); + } + + public SubmissionAttachment findAssignmentAttachmentByIdOrThrow(Long submissionAttachmentId){ + return submissionAttachmentRepository.findById(submissionAttachmentId) + .orElseThrow(() -> new EntityNotFoundException("해당 제출 과제를 찾을수 없습니다.")); + } + + public List findAllAssignment(Long submissionId) { + Submission submission = findByIdOrThrow(submissionId); + List attachments = submissionAttachmentRepository.findAllBySubmission(submission); + return attachments.stream() + .map(sa -> SubmissionAttachmentDto.builder() + .type(sa.getType()) + .value(sa.getValue()) + .originalFileName(sa.getOriginalFileName()) + .contentType(sa.getContentType()) + .size(sa.getSize()) + .build() + ).toList(); + } + + public SubmissionAttachment findsubmissionAttachmentByIdOrThrow(Long submissionAttachmentId) { + return submissionAttachmentRepository.findById(submissionAttachmentId) + .orElseThrow(() -> new EntityNotFoundException("해당 과제 제출 첨부파일을 찾을수 없습니다.")); + } + + public Resource downloadAttachment(SubmissionAttachment submissionAttachment) throws IOException { + String path = submissionAttachment.getValue(); + return fileService.downloadFile(path); + } +} diff --git a/src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java b/src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java new file mode 100644 index 00000000..8f7d5632 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/application/SubmissionQueryService.java @@ -0,0 +1,103 @@ +package hello.cluebackend.domain.submission.application; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.submission.api.dto.request.SubmissionAttachmentUrlDto; +import hello.cluebackend.domain.submission.domain.fileType; +import hello.cluebackend.domain.file.service.FileService; +import hello.cluebackend.domain.submission.domain.Submission; +import hello.cluebackend.domain.submission.domain.SubmissionAttachment; +import hello.cluebackend.domain.submission.persistence.SubmissionRepository; +import hello.cluebackend.domain.classroom.domain.ClassRoom; +import hello.cluebackend.domain.classroom.service.ClassRoomService; +import hello.cluebackend.domain.classroomuser.application.ClassroomUserService; +import hello.cluebackend.domain.submission.persistence.SubmissionAttachmentRepository; +import hello.cluebackend.domain.user.domain.UserEntity; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class SubmissionQueryService { + private final SubmissionRepository submissionRepository; + private final SubmissionAttachmentRepository submissionAttachmentRepository; + private final ClassroomUserService classroomUserService; + private final ClassRoomService classRoomService; + private final FileService fileService; + private final SubmissionCommandService submissionCommandService; + + // 해당 교실 모든 학생에게 과제 부여 & 제출 과제 생성 + @Transactional + public void assignToAllStudentsInClassroom(Long classroomId, Assignment assignment){ + ClassRoom classRoom = classRoomService.findById(classroomId).toEntity(); + List users = classroomUserService.findAllClassroomUser(classRoom); + List submissions = users.stream() + .map(u -> new Submission(assignment, u, false, null)) + .toList(); + submissionRepository.saveAll(submissions); + } + + // 과제 제출하기 + @Transactional + public Submission submitSubmission(Long submissionId) { + Submission submission = submissionCommandService.findByIdOrThrow(submissionId); + submission.submit(); + return submissionRepository.save(submission); + } + + // 과제 제출 취소하기 + @Transactional + public Submission cancelSubmission(Long submissionId) { + Submission submission = submissionCommandService.findByIdOrThrow(submissionId); + submission.cancel(); + return submissionRepository.save(submission); + } + + + // 첨부 파일 추가 + @Transactional + public SubmissionAttachment fileUpload(Long submissionId, MultipartFile file) { + Submission submission = submissionCommandService.findByIdOrThrow(submissionId); + + String storedFiledName = fileService.storeFile(file); + + SubmissionAttachment result = SubmissionAttachment.builder() + .submission(submission) + .type(fileType.file) + .value(storedFiledName) + .originalFileName(file.getOriginalFilename()) + .contentType(file.getContentType()) + .size(file.getSize()) + .build(); + submissionAttachmentRepository.save(result); + return result; + } + + // 첨부 링크 추가 + @Transactional + public SubmissionAttachment linkUpload(Long submissionId, SubmissionAttachmentUrlDto dto) { + Submission submission = submissionCommandService.findByIdOrThrow(submissionId); + + SubmissionAttachment submissionAttachment = SubmissionAttachment.builder() + .submission(submission) + .type(fileType.url) + .value(dto.url()) + .build(); + + submissionAttachmentRepository.save(submissionAttachment); + return submissionAttachment; + } + + // 첨부 파일 삭제 + @Transactional + public void deleteSubmissionAttachment(Long submissionAttachmentId) { + SubmissionAttachment submissionAttachment = submissionCommandService.findAssignmentAttachmentByIdOrThrow(submissionAttachmentId); + if(submissionAttachment.getType() == fileType.file){ + fileService.deleteFile(submissionAttachment.getValue()); + } + submissionAttachmentRepository.delete(submissionAttachment); + } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/submission/domain/BaseEntity.java b/src/main/java/hello/cluebackend/domain/submission/domain/BaseEntity.java new file mode 100644 index 00000000..1d1706cc --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/domain/BaseEntity.java @@ -0,0 +1,32 @@ +package hello.cluebackend.domain.submission.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@EntityListeners(AuditingEntityListener.class) +@MappedSuperclass +@Getter +public class BaseEntity { + @CreatedDate + @Column + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime lastModified; + + @CreatedBy + @Column(updatable = false) + private String createdBy; + + @LastModifiedBy + private String lastModifiedBy; +} diff --git a/src/main/java/hello/cluebackend/domain/submission/domain/Submission.java b/src/main/java/hello/cluebackend/domain/submission/domain/Submission.java new file mode 100644 index 00000000..2def5a55 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/domain/Submission.java @@ -0,0 +1,63 @@ +package hello.cluebackend.domain.submission.domain; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "submission") +@Getter +@AllArgsConstructor +@Builder @Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Submission extends BaseEntity{ + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "submission_id") + private Long submissionId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "assignment_id") + private Assignment assignment; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private UserEntity user; + + @Column(name = "is_submitted") + private boolean isSubmitted; + + @Column(name = "submitted_at", nullable = true) + private LocalDateTime submittedAt; + + public Submission(Assignment assignment, UserEntity user, boolean isSubmitted, LocalDateTime submittedAt) { + if(assignment != null) { changeAssignment(assignment); } + this.user = user; + this.isSubmitted = isSubmitted; + this.submittedAt = submittedAt; + } + + public void changeAssignment(Assignment assignment) { + this.assignment = assignment; + assignment.getSubmissions().add(this); + } + + // 과제 제출 취소 + public void cancel() { + this.isSubmitted = false; + this.submittedAt = LocalDateTime.now(); + } + + // 과제 제출 + public void submit() { + this.isSubmitted = true; + this.submittedAt = LocalDateTime.now(); + } + + // getter + public boolean getIsSubmitted(){ + return this.isSubmitted; + } +} diff --git a/src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java b/src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java new file mode 100644 index 00000000..ed1e0e60 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/domain/SubmissionAttachment.java @@ -0,0 +1,37 @@ +package hello.cluebackend.domain.submission.domain; + +import hello.cluebackend.domain.user.domain.UserEntity; +import jakarta.persistence.*; +import lombok.*; +import org.springframework.core.io.Resource; + +@Entity +@Table(name = "submission_attachment") +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class SubmissionAttachment { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="submission_attachment_id") + private Long SubmissionAttachmentId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="user_id") + private UserEntity user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="submission_id") + private Submission submission; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private fileType type; + + @Column(nullable = false) + private String value; + + private String originalFileName; + private String contentType; + private Long size; +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/submission/domain/fileType.java b/src/main/java/hello/cluebackend/domain/submission/domain/fileType.java new file mode 100644 index 00000000..661ba614 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/domain/fileType.java @@ -0,0 +1,5 @@ +package hello.cluebackend.domain.submission.domain; + +public enum fileType { + file, url +} diff --git a/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionAttachmentRepository.java b/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionAttachmentRepository.java new file mode 100644 index 00000000..a678f749 --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionAttachmentRepository.java @@ -0,0 +1,14 @@ +package hello.cluebackend.domain.submission.persistence; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.submission.domain.Submission; +import hello.cluebackend.domain.submission.domain.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface SubmissionAttachmentRepository extends JpaRepository { + List findAllBySubmission(Submission submission); +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java b/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java new file mode 100644 index 00000000..944565ae --- /dev/null +++ b/src/main/java/hello/cluebackend/domain/submission/persistence/SubmissionRepository.java @@ -0,0 +1,15 @@ +package hello.cluebackend.domain.submission.persistence; + +import hello.cluebackend.domain.assignment.domain.Assignment; +import hello.cluebackend.domain.submission.domain.Submission; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface SubmissionRepository extends JpaRepository { + List findAllByAssignment(Assignment assignment); + + Submission findByAssignment(Assignment assignment); +} diff --git a/src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java b/src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java index e841e58f..b18abe69 100644 --- a/src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java +++ b/src/main/java/hello/cluebackend/domain/user/domain/UserEntity.java @@ -1,14 +1,11 @@ package hello.cluebackend.domain.user.domain; -import hello.cluebackend.domain.classroomuser.domain.ClassRoomUser; import hello.cluebackend.domain.user.presentation.dto.UserDto; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.ColumnDefault; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; @ToString @Entity diff --git a/src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java b/src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java index 77d768d8..d9e76830 100644 --- a/src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java +++ b/src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java @@ -1,5 +1,7 @@ package hello.cluebackend.domain.user.presentation; +import com.nimbusds.oauth2.sdk.TokenResponse; +import hello.cluebackend.domain.user.domain.UserEntity; import hello.cluebackend.domain.user.presentation.dto.DefaultRegisterUserDto; import hello.cluebackend.domain.user.presentation.dto.RegisterUserDto; import hello.cluebackend.domain.user.presentation.dto.UserDto; @@ -43,6 +45,4 @@ public ResponseEntity processRegistration(@RequestBody DefaultRegisterUserDto userService.registerUser(defaultRegisterUserDTO); return new ResponseEntity<>(HttpStatus.CREATED); } - - -} +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/domain/user/presentation/TestController.java b/src/main/java/hello/cluebackend/domain/user/presentation/TestController.java index 86f68bba..3c94efc2 100644 --- a/src/main/java/hello/cluebackend/domain/user/presentation/TestController.java +++ b/src/main/java/hello/cluebackend/domain/user/presentation/TestController.java @@ -24,4 +24,4 @@ public ResponseEntity issueToken(@RequestParam Long userId, @RequestParam Str .header("Authorization", "Bearer " + access) .body("JWT access token and refresh token issued for dev use."); } -} +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/global/common/annotation/CurrentUser.java b/src/main/java/hello/cluebackend/global/common/annotation/CurrentUser.java new file mode 100644 index 00000000..c27eff17 --- /dev/null +++ b/src/main/java/hello/cluebackend/global/common/annotation/CurrentUser.java @@ -0,0 +1,11 @@ +package hello.cluebackend.global.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface CurrentUser { +} diff --git a/src/main/java/hello/cluebackend/global/common/resolver/CurrentUserArgumentResolver.java b/src/main/java/hello/cluebackend/global/common/resolver/CurrentUserArgumentResolver.java new file mode 100644 index 00000000..4456c3b2 --- /dev/null +++ b/src/main/java/hello/cluebackend/global/common/resolver/CurrentUserArgumentResolver.java @@ -0,0 +1,35 @@ +package hello.cluebackend.global.common.resolver; + +import hello.cluebackend.global.common.annotation.CurrentUser; +import hello.cluebackend.global.config.JWTUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +@RequiredArgsConstructor +public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver { + + private final JWTUtil jwtUtil; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(CurrentUser.class) + && parameter.getParameterType().equals(Long.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + String token = jwtUtil.getToken(request); + return jwtUtil.getUserId(token); + } +} diff --git a/src/main/java/hello/cluebackend/global/common/response/ApiResponse.java b/src/main/java/hello/cluebackend/global/common/response/ApiResponse.java new file mode 100644 index 00000000..76897895 --- /dev/null +++ b/src/main/java/hello/cluebackend/global/common/response/ApiResponse.java @@ -0,0 +1,39 @@ +//package hello.cluebackend.global.common.response; +// +//import hello.cluebackend.assignment.management.domain.Assignment; +//import lombok.*; +// +//import java.util.List; +// +//@Getter +//@Builder +//@AllArgsConstructor +//@NoArgsConstructor(access = AccessLevel.PROTECTED) +//public class ApiResponse { +// +// private static final String SUCCESS_STATUS = "success"; +// private static final String FAIL_STATUS = "fail"; +// private static final String ERROR_STATUS = "error"; +// +// private int code; +// private String message; +// private T data; +// +// public ApiResponse(int code, String message, T data){ +// this.code = code; +// this.message = message; +// this.data = data; +// } +// +// public static ApiResponse success(T data){ +// return new ApiResponse<>(, "요청 성공", data); +// } +// +// public static ApiResponse success(ResponseCode code){ +// return new ApiResponse<>(code); +// } +// +// public static List error(String message) { +// return new ApiResponse<>("ERROR", message, null); +// } +//} diff --git a/src/main/java/hello/cluebackend/global/common/response/ResponseCode.java b/src/main/java/hello/cluebackend/global/common/response/ResponseCode.java new file mode 100644 index 00000000..a0247555 --- /dev/null +++ b/src/main/java/hello/cluebackend/global/common/response/ResponseCode.java @@ -0,0 +1,46 @@ +//package hello.cluebackend.global.common.response; +// +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +// +//public enum ResponseCode { +// //== 2xx: 성공 ==// +// SUCCESS(HttpStatus.OK, "요청이 성공적으로 처리되었습니다."), +// CREATED(HttpStatus.CREATED, "리소스가 성공적으로 생성되었습니다."), +// DELETED(HttpStatus.OK, "리소스가 성공적으로 삭제되었습니다."), +// +// //== 4xx: 클라이언트 오류 ==// +// BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), +// UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."), +// FORBIDDEN(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."), +// NOT_FOUND(HttpStatus.NOT_FOUND, "리소스를 찾을 수 없습니다."), +// CONFLICT(HttpStatus.CONFLICT, "요청 충돌이 발생했습니다."), +// +// //== 5xx: 서버 오류 ==// +// INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다."), +// SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE, "서비스를 이용할 수 없습니다."); +// +// private final HttpStatus httpStatus; +// private final String message; +// +// ResponseCode(HttpStatus httpStatus, String message){ +// this.httpStatus = httpStatus; +// this.message = message; +// } +// +// public HttpStatus getHttpStatus() { +// return httpStatus; +// } +// +// public String getMessage() { +// return message; +// } +// +// public int getStatusCode() { +// return httpStatus.value(); +// } +// +// public static ResponseEntity> buildResponse(ResponseCode code, T data){ +// return ResponseEntity.status(code.getHttpStatus()).body(ApiResponse.success(code, data)); +// } +//} diff --git a/src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java b/src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java index bd20a540..97fba870 100644 --- a/src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java +++ b/src/main/java/hello/cluebackend/global/config/CorsMvcConfig.java @@ -7,14 +7,14 @@ @Configuration public class CorsMvcConfig implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry corsRegistry) { - corsRegistry.addMapping("/**") - .exposedHeaders("Set-Cookie") - .allowedOrigins("http://10.129.57.136:3000") -// .allowedOrigins("https://clue-frontend-eight.vercel.app") - .allowedHeaders("*") - .exposedHeaders("Authorization") - .allowCredentials(true); - } -} + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") // 모든 API 경로 허용 +// .allowedOriginPatterns("http://10.150.149.87:7789") // 프론트 IP + 포트 + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드 + .allowedHeaders("*") // 모든 요청 헤더 허용 + .exposedHeaders("Authorization", "Set-Cookie") // 클라이언트가 접근 가능한 헤더 + .allowCredentials(true); // 쿠키, 인증 정보 허용 + } +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/global/config/TestConfig.java b/src/main/java/hello/cluebackend/global/config/TestConfig.java deleted file mode 100644 index 45ca4bb0..00000000 --- a/src/main/java/hello/cluebackend/global/config/TestConfig.java +++ /dev/null @@ -1,5 +0,0 @@ -package hello.cluebackend.global.config; - - -public class TestConfig { -} diff --git a/src/main/java/hello/cluebackend/global/config/WebConfig.java b/src/main/java/hello/cluebackend/global/config/WebConfig.java index 8a88deea..da435cce 100644 --- a/src/main/java/hello/cluebackend/global/config/WebConfig.java +++ b/src/main/java/hello/cluebackend/global/config/WebConfig.java @@ -1,23 +1,32 @@ package hello.cluebackend.global.config; -import org.springframework.beans.factory.annotation.Autowired; +import hello.cluebackend.global.common.resolver.CurrentUserArgumentResolver; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class WebConfig implements WebMvcConfigurer { - private OctetStreamReadMsgConverter octetStreamReadMsgConverter; - @Autowired - public WebConfig(OctetStreamReadMsgConverter octetStreamReadMsgConverter) { - this.octetStreamReadMsgConverter = octetStreamReadMsgConverter; - } + private final OctetStreamReadMsgConverter octetStreamReadMsgConverter; + private final CurrentUserArgumentResolver currentUserArgumentResolver; - @Override - public void configureMessageConverters(List> converters) { - converters.add(octetStreamReadMsgConverter); - } + public WebConfig(OctetStreamReadMsgConverter octetStreamReadMsgConverter, + CurrentUserArgumentResolver currentUserArgumentResolver) { + this.octetStreamReadMsgConverter = octetStreamReadMsgConverter; + this.currentUserArgumentResolver = currentUserArgumentResolver; + } + + @Override + public void configureMessageConverters(List> converters) { + converters.add(octetStreamReadMsgConverter); + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(currentUserArgumentResolver); + } } \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index af892e29..3e197c01 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -2,8 +2,7 @@ spring: application: name: CLUE-Backend datasource: -# url: jdbc:postgresql://localhost:5432/clue - url: ${POSTGRES_URL} + url: ${POSTGRES_URI} username: ${POSTGRES_USER} password: ${POSTGRES_PASSWORD} driver-class-name: org.postgresql.Driver @@ -11,7 +10,7 @@ spring: database-platform: org.hibernate.dialect.PostgreSQLDialect show-sql: true hibernate: - ddl-auto: update + ddl-auto: none data: redis: # host: my_redis