diff --git a/week15/3.practice/exercise/ex1.ts b/week15/3.practice/exercise/ex1.ts new file mode 100644 index 0000000..c8ec209 --- /dev/null +++ b/week15/3.practice/exercise/ex1.ts @@ -0,0 +1,53 @@ +/* 원문(en) + +Intro: + + We are starting a small community of users. For performance + reasons we have decided to store all users right in the code. + This way we can provide our developers with more + user-interaction opportunities. With user-related data, at least. + All the GDPR-related issues will be solved some other day. + This would be the base for our future experiments during + these exercises. + +Exercise: + + Given the data, define the interface "User" and use it accordingly. + +*/ + + +/* + 1번 + + 주어진 데이터에서 'User'라는 이름의 interface를 정의, + 작성한 인터페이스를 코드 내에서 적절하게 사용하기 +*/ + +export type User = unknown; + +export const users: unknown[] = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + } +]; + +export function logPerson(user: unknown) { + console.log(` - ${user.name}, ${user.age}`); +} + +console.log('Users:'); +users.forEach(logPerson); + + +/* In case if you are stuck: + +// https://www.typescriptlang.org/docs/handbook/2/objects.html +*/ diff --git a/week15/3.practice/exercise/ex2.ts b/week15/3.practice/exercise/ex2.ts new file mode 100644 index 0000000..232dadf --- /dev/null +++ b/week15/3.practice/exercise/ex2.ts @@ -0,0 +1,73 @@ +/* 원문(en) + +Intro: + + All 2 users liked the idea of the community. We should go + forward and introduce some order. We are in Germany after all. + Let's add a couple of admins. + + Initially we only had users in the in-memory database. After + introducing Admins, we need to fix the types so that + everything works well together. + +Exercise: + + Type "Person" is missing, please define it and use + it in persons array and logPerson function in order to fix + all the TS errors. + +*/ + +/* + 2번 + + 현재 코드에는 'Person'이라는 Type이 정의(작성)되어 있지 않음 + 따라서 해당 타입을 정의 후, person 배열(array)과 logPerson()에 적용하여 + TS 에러를 해결하기 +*/ + +interface User { + name: string; + age: number; + occupation: string; +} + +interface Admin { + name: string; + age: number; + role: string; +} + +export type Person = unknown; + +export const persons: User[] /* <- Person[] */ = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + name: 'Bruce Willis', + age: 64, + role: 'World saver' + } +]; + +export function logPerson(user: User) { + console.log(` - ${user.name}, ${user.age}`); +} + +persons.forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/types-from-types.html \ No newline at end of file diff --git a/week15/3.practice/exercise/ex3.ts b/week15/3.practice/exercise/ex3.ts new file mode 100644 index 0000000..591ca89 --- /dev/null +++ b/week15/3.practice/exercise/ex3.ts @@ -0,0 +1,81 @@ +/* 원문(en) + +Intro: + + Since we already have some of the additional + information about our users, it's a good idea + to output it in a nice way. + +Exercise: + + Fix type errors in logPerson function. + + logPerson function should accept both User and Admin + and should output relevant information according to + the input: occupation for User and role for Admin. + +*/ + +/* + 3번 + + logPerson()의 타입 에러 해결하기 + logPerson()은 User와 Admin 인터페이스를 + 모두 인수로 전달받을 수 있어야하고, + + 전달받은 인수의 값(input value)에 따라 적절한 값(output value)을 반환해야함 + ex) User 타입일 경우, occupation 프로퍼티 + Admin 타입일 경우, role 프로퍼티 +*/ + +interface User { + name: string; + age: number; + occupation: string; +} + +interface Admin { + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + name: 'Bruce Willis', + age: 64, + role: 'World saver' + } +]; + +export function logPerson(person: Person) { + let additionalInformation: string; + if (person.role) { + additionalInformation = person.role; + } else { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +persons.forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing \ No newline at end of file diff --git a/week15/3.practice/exercise/ex4.ts b/week15/3.practice/exercise/ex4.ts new file mode 100644 index 0000000..b53fcb5 --- /dev/null +++ b/week15/3.practice/exercise/ex4.ts @@ -0,0 +1,76 @@ +/* 원문(en) + +Intro: + + As we introduced "type" to both User and Admin + it's now easier to distinguish between them. + Once object type checking logic was extracted + into separate functions isUser and isAdmin - + logPerson function got new type errors. + +Exercise: + + Figure out how to help TypeScript understand types in + this situation and apply necessary fixes. + +*/ + +/* + 4번 + + 문제의 코드에서 TypeScript가 + 타입을 이해할 수 있도록 코드를 수정해보기 +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' }, + { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }, + { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' } +]; + +export function isAdmin(person: Person) { + return person.type === 'admin'; +} + +export function isUser(person: Person) { + return person.type === 'user'; +} + +export function logPerson(person: Person) { + let additionalInformation: string = ''; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +console.log('Admins:'); +persons.filter(isAdmin).forEach(logPerson); + +console.log(); + +console.log('Users:'); +persons.filter(isUser).forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates diff --git a/week15/3.practice/exercise/ex5.ts b/week15/3.practice/exercise/ex5.ts new file mode 100644 index 0000000..12ebda7 --- /dev/null +++ b/week15/3.practice/exercise/ex5.ts @@ -0,0 +1,124 @@ +/* 원문(En) + +Intro: + + Time to filter the data! In order to be flexible + we filter users using a number of criteria and + return only those matching all of the criteria. + We don't need Admins yet, we only filter Users. + +Exercise: + + Without duplicating type structures, modify + filterUsers function definition so that we can + pass only those criteria which are needed, + and not the whole User information as it is + required now according to typing. + +Higher difficulty bonus exercise: + + Exclude "type" from filter criterias. + +*/ + +/* + 5번 + + 타입 구조를 새로 추가 정의하지 않고, + filterUsers() 함수 정의(선언부 부분, 파라미터)를 수정하여 + 필요한 기준(criteria)에만 부합하는 파라미터를 전달할 수 있도록 구현 + + * 부가 설명 + 현재 작성된 코드는 전체 사용자 정보(User information)를 받고 있지만, + 정말 필요한 기준의 값만 전달할 수 있도록 구현하라는 의미 + + ** High difficulty bonus exercise + Exclude "type" from filter criterias. + +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { + type: 'admin', + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + type: 'user', + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + type: 'admin', + name: 'Bruce Willis', + age: 64, + role: 'World saver' + }, + { + type: 'user', + name: 'Wilson', + age: 23, + occupation: 'Ball' + }, + { + type: 'admin', + name: 'Agent Smith', + age: 23, + role: 'Administrator' + } +]; + +export const isAdmin = (person: Person): person is Admin => person.type === 'admin'; +export const isUser = (person: Person): person is User => person.type === 'user'; + +export function logPerson(person: Person) { + let additionalInformation = ''; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +export function filterUsers(persons: Person[], criteria: User): User[] { + return persons.filter(isUser).filter((user) => { + const criteriaKeys = Object.keys(criteria) as (keyof User)[]; + return criteriaKeys.every((fieldName) => { + return user[fieldName] === criteria[fieldName]; + }); + }); +} + +console.log('Users of age 23:'); + +filterUsers( + persons, + { + age: 23 + } +).forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/utility-types.html +// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types \ No newline at end of file diff --git a/week15/3.practice/exercise/ex6.ts b/week15/3.practice/exercise/ex6.ts new file mode 100644 index 0000000..cf9b30d --- /dev/null +++ b/week15/3.practice/exercise/ex6.ts @@ -0,0 +1,104 @@ +/* 원문(en) + +Intro: + + Filtering requirements have grown. We need to be + able to filter any kind of Persons. + +Exercise: + + Fix typing for the filterPersons so that it can filter users + and return User[] when personType='user' and return Admin[] + when personType='admin'. Also filterPersons should accept + partial User/Admin type according to the personType. + `criteria` argument should behave according to the + `personType` argument value. `type` field is not allowed in + the `criteria` field. + +Higher difficulty bonus exercise: + + Implement a function `getObjectKeys()` which returns more + convenient result for any argument given, so that you don't + need to cast it. + + let criteriaKeys = Object.keys(criteria) as (keyof User)[]; + --> + let criteriaKeys = getObjectKeys(criteria); + +*/ + +/* + 6번 + + filterPersons()를 수정해서 user를 필터링할 수 있고, personType이 'user'일 때는 + User[]을, personType이 'admin'일 때는 Admin[]을 반환하도록 구현 + + 또한 filterPersons()는 인수(파라미터) 'personType'에 따라 부분적으로 User 혹은 Admin 타입을 받을 수 있어야함 + + criteria 인수는 personType의 인수에 따라 함수의 동작이 달라져야함 + 제약조건: 'type' 필드는 criteria 필드에 허용될 수 없음 + + Higher difficulty bonus exercise:(미번역) + +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' }, + { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }, + { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }, + { type: 'user', name: 'Wilson', age: 23, occupation: 'Ball' }, + { type: 'admin', name: 'Agent Smith', age: 23, role: 'Anti-virus engineer' } +]; + +export function logPerson(person: Person) { + console.log( + ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}` + ); +} + +export function filterPersons(persons: Person[], personType: string, criteria: unknown): unknown[] { + return persons + .filter((person) => person.type === personType) + .filter((person) => { + let criteriaKeys = Object.keys(criteria) as (keyof Person)[]; + return criteriaKeys.every((fieldName) => { + return person[fieldName] === criteria[fieldName]; + }); + }); +} + +// +export function getObjectKeys(criteria: ) { + +} + +export const usersOfAge23 = filterPersons(persons, 'user', { age: 23 }); +export const adminsOfAge23 = filterPersons(persons, 'admin', { age: 23 }); + +console.log('Users of age 23:'); +usersOfAge23.forEach(logPerson); + +console.log(); + +console.log('Admins of age 23:'); +adminsOfAge23.forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads diff --git a/week15/3.practice/exercise/tsconfig.json b/week15/3.practice/exercise/tsconfig.json new file mode 100644 index 0000000..1109fb2 --- /dev/null +++ b/week15/3.practice/exercise/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "strict": true, + "noEmitOnError": true, + "lib": ["ESNext", "DOM"] + } +} diff --git a/week15/3.practice/solution/ex1.ts b/week15/3.practice/solution/ex1.ts new file mode 100644 index 0000000..c795f1c --- /dev/null +++ b/week15/3.practice/solution/ex1.ts @@ -0,0 +1,59 @@ +/* 원문(en) + +Intro: + + We are starting a small community of users. For performance + reasons we have decided to store all users right in the code. + This way we can provide our developers with more + user-interaction opportunities. With user-related data, at least. + All the GDPR-related issues will be solved some other day. + This would be the base for our future experiments during + these exercises. + +Exercise: + + Given the data, define the interface "User" and use it accordingly. + +*/ + + +/* + 1번 + + 주어진 데이터에서 'User'라는 이름의 interface를 정의, + 작성한 인터페이스를 코드 내에서 적절하게 사용하기 +*/ + +export type User = { + name: String, + age: Number, + occupation: String, +}; + +export const users: User[] = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + } +]; + +export function logPerson(user: User) { + console.log(` - ${user.name}, ${user.age}`); +} + +console.log('Users:'); +users.forEach(logPerson); + + +/* In case if you are stuck: + +// https://www.typescriptlang.org/docs/handbook/2/objects.html +*/ + +// Q. interface와 type의 차이? \ No newline at end of file diff --git a/week15/3.practice/solution/ex2.ts b/week15/3.practice/solution/ex2.ts new file mode 100644 index 0000000..08fd53a --- /dev/null +++ b/week15/3.practice/solution/ex2.ts @@ -0,0 +1,73 @@ +/* 원문(en) + +Intro: + + All 2 users liked the idea of the community. We should go + forward and introduce some order. We are in Germany after all. + Let's add a couple of admins. + + Initially we only had users in the in-memory database. After + introducing Admins, we need to fix the types so that + everything works well together. + +Exercise: + + Type "Person" is missing, please define it and use + it in persons array and logPerson function in order to fix + all the TS errors. + +*/ + +/* + 2번 + + 현재 코드에는 'Person'이라는 Type이 정의(작성)되어 있지 않음(41번 라인) + 따라서 해당 타입을 정의 후, person 배열(array)과 logPerson()에 적용하여 + TS 에러를 해결하기 +*/ + +interface User { + name: string; + age: number; + occupation: string; +} + +interface Admin { + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + name: 'Bruce Willis', + age: 64, + role: 'World saver' + } +]; + +export function logPerson(person: Person) { + console.log(` - ${person.name}, ${person.age}`); +} + +persons.forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/types-from-types.html \ No newline at end of file diff --git a/week15/3.practice/solution/ex3.ts b/week15/3.practice/solution/ex3.ts new file mode 100644 index 0000000..6976878 --- /dev/null +++ b/week15/3.practice/solution/ex3.ts @@ -0,0 +1,82 @@ +/* 원문(en) + +Intro: + + Since we already have some of the additional + information about our users, it's a good idea + to output it in a nice way. + +Exercise: + + Fix type errors in logPerson function. + + logPerson function should accept both User and Admin + and should output relevant information according to + the input: occupation for User and role for Admin. + +*/ + +/* + 3번 + + logPerson()의 타입 에러 해결하기 + logPerson()은 User와 Admin 인터페이스를 + 모두 인수로 전달받을 수 있어야하고, + + 전달받은 인수의 값(input value)에 따라 적절한 값(output value)을 반환해야함 + ex) User 타입일 경우, occupation 프로퍼티 + Admin 타입일 경우, role 프로퍼티 +*/ + +interface User { + name: string; + age: number; + occupation: string; +} + +interface Admin { + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { + name: 'Max Mustermann', + age: 25, + occupation: 'Chimney sweep' + }, + { + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + name: 'Bruce Willis', + age: 64, + role: 'World saver' + } +]; + +export function logPerson(person: Person) { + let additionalInformation: string; + // if (person.role) { // Property 'role' does not exist on type 'Person'. -> person으로 전달받은 인수가 User interface 타입일 경우 role 프로퍼티가 존재하지 않을 수 있음 + if ('role' in person) { // person의 타입을 좁혀서(Narrowing) role이라는 프로퍼티가 존재할 때만 조건식이 성립하도록 변경하여 해결 + additionalInformation = person.role; + } else { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +persons.forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing \ No newline at end of file diff --git a/week15/3.practice/solution/ex4.ts b/week15/3.practice/solution/ex4.ts new file mode 100644 index 0000000..f2f6907 --- /dev/null +++ b/week15/3.practice/solution/ex4.ts @@ -0,0 +1,78 @@ +/* 원문(en) + +Intro: + + As we introduced "type" to both User and Admin + it's now easier to distinguish between them. + Once object type checking logic was extracted + into separate functions isUser and isAdmin - + logPerson function got new type errors. + +Exercise: + + Figure out how to help TypeScript understand types in + this situation and apply necessary fixes. + +*/ + +/* + 4번 + + 문제의 코드에서 TypeScript가 + 타입을 이해할 수 있도록 코드를 수정해보기 +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' }, + { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }, + { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' } +]; + +export function isAdmin(person: Person): person is Admin // 반환 타입으로 타입 예측자(type predicate)를 적용하여 true/false 뿐만 아니라, + // TS 타입 검사기에게 person 객체가 'Admin'타입임을 알려주게 되고, true를 반환할 때, person 객체가 Admin 타입으로 변환됨 +{ + return person.type === 'admin'; +} + +export function isUser(person: Person): person is User { + return person.type === 'user'; +} + +export function logPerson(person: Person) { + let additionalInformation: string = ''; + if (isAdmin(person)) { // + additionalInformation = person.role; // Property 'role' does not exist on type 'Person' & 'User' -> role 프로퍼티는 User 인터페이스 타입에 존재하지 않을 수 있음 + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +console.log('Admins:'); +persons.filter(isAdmin).forEach(logPerson); + +console.log(); + +console.log('Users:'); +persons.filter(isUser).forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates diff --git a/week15/3.practice/solution/ex5.ts b/week15/3.practice/solution/ex5.ts new file mode 100644 index 0000000..71ee139 --- /dev/null +++ b/week15/3.practice/solution/ex5.ts @@ -0,0 +1,125 @@ +/* 원문(en) + +Intro: + + Filtering requirements have grown. We need to be + able to filter any kind of Persons. + +Exercise: + + Fix typing for the filterPersons so that it can filter users + and return User[] when personType='user' and return Admin[] + when personType='admin'. Also filterPersons should accept + partial User/Admin type according to the personType. + `criteria` argument should behave according to the + `personType` argument value. `type` field is not allowed in + the `criteria` field. + +Higher difficulty bonus exercise: + + Implement a function `getObjectKeys()` which returns more + convenient result for any argument given, so that you don't + need to cast it. + + let criteriaKeys = Object.keys(criteria) as (keyof User)[]; + --> + let criteriaKeys = getObjectKeys(criteria); + +*/ + +/* + 6번 + + filterPersons()를 수정해서 user를 필터링할 수 있고, personType이 'user'일 때는 + User[]을, personType이 'admin'일 때는 Admin[]을 반환하도록 구현 + + 또한 filterPersons()는 인수(파라미터) 'personType'에 따라 부분적으로 User 혹은 Admin 타입을 받을 수 있어야함 + + criteria 인수는 personType의 인수에 따라 함수의 동작이 달라져야함 + 제약조건: 'type' 필드는 criteria 필드에 허용될 수 없음 + + Higher difficulty bonus exercise:(미번역) +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { + type: 'admin', + name: 'Jane Doe', + age: 32, + role: 'Administrator' + }, + { + type: 'user', + name: 'Kate Müller', + age: 23, + occupation: 'Astronaut' + }, + { + type: 'admin', + name: 'Bruce Willis', + age: 64, + role: 'World saver' + }, + { + type: 'user', + name: 'Wilson', + age: 23, + occupation: 'Ball' + }, + { + type: 'admin', + name: 'Agent Smith', + age: 23, + role: 'Administrator' + } +]; + +export const isAdmin = (person: Person): person is Admin => person.type === 'admin'; +export const isUser = (person: Person): person is User => person.type === 'user'; + +export function logPerson(person: Person) { + let additionalInformation = ''; + if (isAdmin(person)) { + additionalInformation = person.role; + } + if (isUser(person)) { + additionalInformation = person.occupation; + } + console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`); +} + +// Partial로 해결 +export function filterUsers(persons: Person[], criteria: Partial): User[] { + return persons.filter(isUser).filter((user) => { + const criteriaKeys = Object.keys(criteria) as (keyof User)[]; + return criteriaKeys.every((fieldName) => { + return user[fieldName] === criteria[fieldName]; + }); + }); +} + +console.log('Users of age 23:'); + +filterUsers( + persons, + { + age: 23 + } +).forEach(logPerson); \ No newline at end of file diff --git a/week15/3.practice/solution/ex6.ts b/week15/3.practice/solution/ex6.ts new file mode 100644 index 0000000..f8dd6f8 --- /dev/null +++ b/week15/3.practice/solution/ex6.ts @@ -0,0 +1,108 @@ +/* 원문(en) + +Intro: + + Filtering requirements have grown. We need to be + able to filter any kind of Persons. + +Exercise: + + Fix typing for the filterPersons so that it can filter users + and return User[] when personType='user' and return Admin[] + when personType='admin'. Also filterPersons should accept + partial User/Admin type according to the personType. + `criteria` argument should behave according to the + `personType` argument value. `type` field is not allowed in + the `criteria` field. + +Higher difficulty bonus exercise: + + Implement a function `getObjectKeys()` which returns more + convenient result for any argument given, so that you don't + need to cast it. + + let criteriaKeys = Object.keys(criteria) as (keyof User)[]; + --> + let criteriaKeys = getObjectKeys(criteria); + +*/ + +/* + 6번 + + filterPersons()를 수정해서 user를 필터링할 수 있고, personType이 'user'일 때는 + User[]을, personType이 'admin'일 때는 Admin[]을 반환하도록 구현 + + 또한 filterPersons()는 인수(파라미터) 'personType'에 따라 부분적으로 User 타입을 받거나, Admin 타입을 받을 수도 있어야함(오버로딩 개념 활용) + + criteria 인수는 personType의 인수에 따라 함수의 동작이 달라져야함 + 제약조건: 'type' 필드는 criteria 필드에 허용될 수 없음 + + Higher difficulty bonus exercise:(미번역) + +*/ + +interface User { + type: 'user'; + name: string; + age: number; + occupation: string; +} + +interface Admin { + type: 'admin'; + name: string; + age: number; + role: string; +} + +export type Person = User | Admin; + +export const persons: Person[] = [ + { type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' }, + { type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' }, + { type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }, + { type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }, + { type: 'user', name: 'Wilson', age: 23, occupation: 'Ball' }, + { type: 'admin', name: 'Agent Smith', age: 23, role: 'Anti-virus engineer' } +]; + +export function logPerson(person: Person) { + console.log( + ` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}` + ); +} + +// ex) filterPersons(persons, 'user', { age: 23 }); // admin이 아닌 user 리스트에서 age가 23인 user만 필터링 +export function filterPersons(persons: Person[], personType: string, criteria: object): Person[] { + return persons + .filter((person) => person.type === personType) // personType('user' or 'admin')으로 user와 admin 중 하나로 필터링 + .filter((person) => { + let criteriaKeys = getObjectKey(criteria);// 인수 criteria의 타입을 object로 하면 이 줄은 해결 가능하나, 아래의 에러 발생 + return criteriaKeys.every((fieldName) => { + return person[fieldName] === criteria[fieldName]; + // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Person'. + // No index signature with a parameter of type 'string' was found on type '{}'. + }); + }); +} + +// const getObjectKeys = (obj: T) => Object.keys(obj) as (keyof T)[]; +export function getObjectKey(criteria: T) { + return Object.keys(criteria) as (keyof T)[]; // as (keyof T)[]을 뒤에 추가하지 않으면 filterPersons()의 최종 return부분에서 하단(Line86~)에 작성된(ELement implicitly~)에러 발생 + // 다른 해결 방법으로는 User, Admin 인터페이스의 필드 부분에 각각 인덱스 시그니처인 [key: string]: string | number;를 추가하여 해결 가능 +} + +export const usersOfAge23 = filterPersons(persons, 'user', { age: 23 }); +export const adminsOfAge23 = filterPersons(persons, 'admin', { age: 23 }); + +console.log('Users of age 23:'); +usersOfAge23.forEach(logPerson); + +console.log(); + +console.log('Admins of age 23:'); +adminsOfAge23.forEach(logPerson); + +// In case if you are stuck: +// https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads diff --git a/week15/sample-spring-app-gradle/.gitattributes b/week15/sample-spring-app-gradle/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/week15/sample-spring-app-gradle/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/week15/sample-spring-app-gradle/.gitignore b/week15/sample-spring-app-gradle/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/week15/sample-spring-app-gradle/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/week15/sample-spring-app-gradle/Dockerfile b/week15/sample-spring-app-gradle/Dockerfile new file mode 100644 index 0000000..4e28afa --- /dev/null +++ b/week15/sample-spring-app-gradle/Dockerfile @@ -0,0 +1,7 @@ +FROM openjdk:17-jdk-alpine +ARG JAR_FILE=/build/libs/*.jar +COPY ${JAR_FILE} /app.jar +ENTRYPOINT ["java", "-jar","app.jar"] + +#docker tag local-image:tagname new-repo:tagname +#docker push new-repo:tagname diff --git a/week15/sample-spring-app-gradle/README.md b/week15/sample-spring-app-gradle/README.md new file mode 100644 index 0000000..dd47bbc --- /dev/null +++ b/week15/sample-spring-app-gradle/README.md @@ -0,0 +1 @@ +# sample-spring-app-gradle diff --git a/week15/sample-spring-app-gradle/SD-LEESEUNGJUN-rsa-key.pem b/week15/sample-spring-app-gradle/SD-LEESEUNGJUN-rsa-key.pem new file mode 100644 index 0000000..01d573a --- /dev/null +++ b/week15/sample-spring-app-gradle/SD-LEESEUNGJUN-rsa-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAqlgKjF5oqA6nd9umP4Imzy+wmLUmVHWvdY3bVe2m5ZROzc6O +ppKK0LBlnijYURHCb+ttOAXAn5u3ROlGpxyUfnp1BfhQOx8Ws5DbWDRLXfNoJOAX +nupPDnV45Mk1gMyggERwG+X9JhPnOwbDE0EB9kjEuGbW4JUR3IymgmwGTVhYurb6 +wHkWDokTfiXMMUlQES7+NNU6qQubXTEu4b+eazkVy8AjmCMiUl90aHE1GOUUilf4 +Nqkzswvmn48P+ZVeyfnBWgsK9xApmzPLPlNhEot8Tks+XnYqDz8wk1nxr56YlSYe +CRJ8Y8hU2TOBYfE8v2T2K486P9I7RyKsJxMgvwIDAQABAoIBADcSZ7ah/yv2MOZr +OQBnT6zEOk/koRPFSIi06CiLwN2QaTnYgf4QepilrvTHN8hd+YK9ln36PiBb85Bv +6su0Tb3mUVcwxJ+YmXpMZt3JamRhgVYKrRhPoGeISyGMe6O8hFdzyNr8LC/XV3aZ +5Q+ggMXjGNaVSPsqtq+bOWoJCrCOTHnmBOGEaEYEiMGLHS0njTxsrHNCxGo5sR5j +2LAuAoC+IaZukWXW7+MF/P8c0FqDWo3C5Sl0vt4PF7hbrP1p87gE8QL5KfEdblWr +spLB1S0KZF0hhchZHA6/0Y45NkyXqqJAP35WHs6hUCz+8q6IAZYCt1J50CSdnA6R +kOcZ8MECgYEA0dpQfXT/OB2flFT179c2cJ1zYVzibMfWHZ7DtOz5JJHtpBVUTl/h +DL+IxVX77F0lkfTCyV2qlb+56NnGAGWjqdooOHY2OJi6j622WTrgsu7skNZCF6J1 +LdhKF5e6WLEuf2OdXS4OQxa3IGx6iP8VZCSadd+E77+bf1vMRMQY5QcCgYEAz82R +88FiQroAFM+7KT2M+NNBtztrY7yZJ21FbC6Xwew7axWs7zdJmk9x6ssy7zxbEax5 +KkCKhrfywt2q1rrD7ODUFW60CyC44E5ROJ3zn5sMLN024kcPeqVLaPFw22BgpHSI +M11XSblSwPAwTI7OUAB422Ic6hpc5/HwYyAm8IkCgYBiWdodYkb3CtMulr8I4Clw +kCpyODEaYHIWQcFhOyrtG8NvgluXu9HiwqWcLtc6CNHB3R+pdk+rjUbZPa9RolEZ +Gth81dJFpjbRfc2XdkO3OMkAfJpPL2GR0euvsPmx9aBgLdSjxiEDClNbS9wF9t8U +px8JHW/VWUzMXL6Yf/tW4wKBgQCpBxG9MIP4usruIrqepQ5SJ6TAoniLvBDn3R4n +DQ8sEjeStyLfwh0Ag7apwXdA2dtJ2P0FvVHwmxfAIgage3J677YTaOTSWF+JNKsk +XnaYUMnqhFXsYNjabjGUvy+Jgi+aFZ/kB/zEXxZNEhJ3c4Gl2Xx1Fq1mTuNbV5St +TK3f4QKBgQCrWIuwZELm+JRAXkow0iX1COzPZh50vqRFFOIoA+hVd0viuQJY19qc +9ID5GxLn980sNlWgeDGuadTADjgMGcsT2l/6WTtgtFLp9ptMLVxVsrxJeR/Fpehf +xuwksQxELtRW0KtH3kp3l0BKg51QSjoldQgiVm8yuNq8xYlqjXpoag== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/week15/sample-spring-app-gradle/build.gradle b/week15/sample-spring-app-gradle/build.gradle new file mode 100644 index 0000000..0d86530 --- /dev/null +++ b/week15/sample-spring-app-gradle/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.3.4' + id 'io.spring.dependency-management' version '1.1.6' +} + +group = 'com.example' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/week15/sample-spring-app-gradle/gradle/wrapper/gradle-wrapper.jar b/week15/sample-spring-app-gradle/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a4b76b9 Binary files /dev/null and b/week15/sample-spring-app-gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/week15/sample-spring-app-gradle/gradle/wrapper/gradle-wrapper.properties b/week15/sample-spring-app-gradle/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..df97d72 --- /dev/null +++ b/week15/sample-spring-app-gradle/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/week15/sample-spring-app-gradle/gradlew b/week15/sample-spring-app-gradle/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/week15/sample-spring-app-gradle/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/week15/sample-spring-app-gradle/gradlew.bat b/week15/sample-spring-app-gradle/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/week15/sample-spring-app-gradle/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/week15/sample-spring-app-gradle/settings.gradle b/week15/sample-spring-app-gradle/settings.gradle new file mode 100644 index 0000000..0a383dd --- /dev/null +++ b/week15/sample-spring-app-gradle/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'demo' diff --git a/week15/sample-spring-app-gradle/src/main/java/com/example/demo/DemoApplication.java b/week15/sample-spring-app-gradle/src/main/java/com/example/demo/DemoApplication.java new file mode 100644 index 0000000..64b538a --- /dev/null +++ b/week15/sample-spring-app-gradle/src/main/java/com/example/demo/DemoApplication.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/week15/sample-spring-app-gradle/src/main/java/com/example/demo/HelloController.java b/week15/sample-spring-app-gradle/src/main/java/com/example/demo/HelloController.java new file mode 100644 index 0000000..ee21c29 --- /dev/null +++ b/week15/sample-spring-app-gradle/src/main/java/com/example/demo/HelloController.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping("/hello") + public String getHello() { + return "Hello!"; + } +} \ No newline at end of file diff --git a/week15/sample-spring-app-gradle/src/main/resources/application.properties b/week15/sample-spring-app-gradle/src/main/resources/application.properties new file mode 100644 index 0000000..2109a44 --- /dev/null +++ b/week15/sample-spring-app-gradle/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=demo diff --git a/week15/sample-spring-app-gradle/src/test/java/com/example/demo/DemoApplicationTests.java b/week15/sample-spring-app-gradle/src/test/java/com/example/demo/DemoApplicationTests.java new file mode 100644 index 0000000..2778a6a --- /dev/null +++ b/week15/sample-spring-app-gradle/src/test/java/com/example/demo/DemoApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DemoApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/week15/sonarqube/sonar-demo/.gitattributes b/week15/sonarqube/sonar-demo/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/week15/sonarqube/sonar-demo/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/week15/sonarqube/sonar-demo/.gitignore b/week15/sonarqube/sonar-demo/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/week15/sonarqube/sonar-demo/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/week15/sonarqube/sonar-demo/build.gradle b/week15/sonarqube/sonar-demo/build.gradle new file mode 100644 index 0000000..ac5d109 --- /dev/null +++ b/week15/sonarqube/sonar-demo/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.4' + id 'io.spring.dependency-management' version '1.1.7' + id 'org.sonarqube' version '6.0.1.5171' +} + +group = 'dev.sonar' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/week15/sonarqube/sonar-demo/docker-compose.yml b/week15/sonarqube/sonar-demo/docker-compose.yml new file mode 100644 index 0000000..ba3801d --- /dev/null +++ b/week15/sonarqube/sonar-demo/docker-compose.yml @@ -0,0 +1,36 @@ +services: + sonarqube: + image: sonarqube:10.6-community # 소나큐브 커뮤니티 버전 이미지를 활용 + depends_on: + - sonar_db # sonar_db라는 이름의 service 식별자에 의존, 해당 의존성을 가진 이미지 기반의 컨테이너가 먼저 실행 되어야 함 + environment: # DB 연결 설정 정보 + SONAR_JDBC_URL: jdbc:postgresql://sonar_db:5432/sonar + SONAR_JDBC_USERNAME: sonar + SONAR_JDBC_PASSWORD: sonar + ports: + - "9000:9000" + volumes: # SQ 서버에서 볼륨을 통해 실행과정에서 발생하는 데이터들을 관리,저장할 디렉토리 경로 + - sonarqube_conf:/opt/sonarqube/conf + - sonarqube_data:/opt/sonarqube/data + - sonarqube_extensions:/opt/sonarqube/extensions + - sonarqube_logs:/opt/sonarqube/logs + - sonarqube_temp:/opt/sonarqube/temp + + sonar_db: # SQ 서버가 활용할 DBMS 설정 + image: postgres:16 + environment: + POSTGRES_USER: sonar + POSTGRES_PASSWORD: sonar + POSTGRES_DB: sonar + volumes: + - sonar_db:/var/lib/postgresql + - sonar_db_data:/var/lib/postgresql/data + +volumes: + sonarqube_conf: + sonarqube_data: + sonarqube_extensions: + sonarqube_logs: + sonarqube_temp: + sonar_db: + sonar_db_data: diff --git a/week15/sonarqube/sonar-demo/gradle/wrapper/gradle-wrapper.jar b/week15/sonarqube/sonar-demo/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..9bbc975 Binary files /dev/null and b/week15/sonarqube/sonar-demo/gradle/wrapper/gradle-wrapper.jar differ diff --git a/week15/sonarqube/sonar-demo/gradle/wrapper/gradle-wrapper.properties b/week15/sonarqube/sonar-demo/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37f853b --- /dev/null +++ b/week15/sonarqube/sonar-demo/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/week15/sonarqube/sonar-demo/gradlew b/week15/sonarqube/sonar-demo/gradlew new file mode 100755 index 0000000..faf9300 --- /dev/null +++ b/week15/sonarqube/sonar-demo/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/week15/sonarqube/sonar-demo/gradlew.bat b/week15/sonarqube/sonar-demo/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/week15/sonarqube/sonar-demo/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/week15/sonarqube/sonar-demo/index.html b/week15/sonarqube/sonar-demo/index.html new file mode 100644 index 0000000..2abe342 --- /dev/null +++ b/week15/sonarqube/sonar-demo/index.html @@ -0,0 +1,81 @@ + + + + + + Markdown Render + + + + + + + + + + + + + + + + + + + + + +
+ +

객체 타입에도 유니언과 내로잉을 적용하여 잠재적 에러를 줄이고 코드의 안전성을 높일 수 있습니다.

+ +

객체 타입에 유니언 적용하기

+ + +type CookieWithCompany = { +name: string; +company: string; +} + +type CookieWithTaste = { +name: string; +taste: string; +} + +type Cookie = CookieWithCompany | CookieWithTaste; + +const firstCookie: Cookie = Math.random() > 0.5 +? { name: 'OREO', company: 'Nabisco' } +: { name: 'OREO', taste: 'Choco' }; + +firstCookie.name; // ✅ OK +firstCookie.company; // ❌ ERROR +if ('company' in firstCookie) { +firstCookie.company; +} else { +firstCookie.taste; +} + + + +
+ + + \ No newline at end of file diff --git a/week15/sonarqube/sonar-demo/settings.gradle b/week15/sonarqube/sonar-demo/settings.gradle new file mode 100644 index 0000000..f2f2bfc --- /dev/null +++ b/week15/sonarqube/sonar-demo/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'sonar-demo' diff --git a/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/Ex1.java b/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/Ex1.java new file mode 100644 index 0000000..1bf7c48 --- /dev/null +++ b/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/Ex1.java @@ -0,0 +1,23 @@ + +package dev.sonar; + +import java.util.logging.Logger; + +public class Ex1 { + + Logger logger = Logger.getLogger("Ex1"); + + void run() throws InterruptedException { + boolean flag = true; + int a = 1; + + while (flag) { + logger.info("Hello!"); + Thread.sleep(5000); + + if (a == 1) { + flag = false; + } + } + } +} diff --git a/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/Ex2.java b/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/Ex2.java new file mode 100644 index 0000000..29aada1 --- /dev/null +++ b/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/Ex2.java @@ -0,0 +1,4 @@ +package dev.sonar; + +public class Ex2 { +} diff --git a/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/SonarDemoApplication.java b/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/SonarDemoApplication.java new file mode 100644 index 0000000..ee0561b --- /dev/null +++ b/week15/sonarqube/sonar-demo/src/main/java/dev/sonar/SonarDemoApplication.java @@ -0,0 +1,13 @@ +package dev.sonar; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SonarDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(SonarDemoApplication.class, args); + } + +} diff --git a/week15/sonarqube/sonar-demo/src/main/resources/application.properties b/week15/sonarqube/sonar-demo/src/main/resources/application.properties new file mode 100644 index 0000000..3c9e52f --- /dev/null +++ b/week15/sonarqube/sonar-demo/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=sonar-demo diff --git a/week15/sonarqube/sonar-demo/src/test/java/dev/sonar/SonarDemoApplicationTests.java b/week15/sonarqube/sonar-demo/src/test/java/dev/sonar/SonarDemoApplicationTests.java new file mode 100644 index 0000000..1b80a7f --- /dev/null +++ b/week15/sonarqube/sonar-demo/src/test/java/dev/sonar/SonarDemoApplicationTests.java @@ -0,0 +1,13 @@ +package dev.sonar; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SonarDemoApplicationTests { + + @Test + void contextLoads() { + } + +}