<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sseop.log</title>
        <link>https://velog.io/</link>
        <description>Founder @Planby</description>
        <lastBuildDate>Fri, 22 Apr 2022 00:53:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sseop.log</title>
            <url>https://velog.velcdn.com/images/dev_sseop-e/profile/86497a7f-1b8f-4b9c-8567-695caf5bdc0e/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sseop.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_sseop-e" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Python Web Scraping (Beautifulsoup)]]></title>
            <link>https://velog.io/@dev_sseop-e/Python-Web-Scraping</link>
            <guid>https://velog.io/@dev_sseop-e/Python-Web-Scraping</guid>
            <pubDate>Fri, 22 Apr 2022 00:53:09 GMT</pubDate>
            <description><![CDATA[<h3 id="urls">URLs</h3>
<p>Website</p>
<pre><code>URL_HOLDER = &#39;https://polygonscan.com/token/0x6A8Ec2d9BfBDD20A7F5A4E89D640F7E7cebA4499&#39;</code></pre><p>Open api</p>
<pre><code>URL_PRICE = &#39;https://openapi.digifinex.com/v3/ticker&#39;
URL_CURRENCY = &#39;https://api.exchangerate-api.com/v4/latest/USD&#39;</code></pre><p>Target file path</p>
<pre><code>FILE_PATH = &#39;/var/www/html/customer/globalmsq.com/public_html/index.html&#39;</code></pre><h3 id="holder-scrap">Holder scrap</h3>
<p>Find by class and stringify the data. By data processing, pick only required value.</p>
<pre><code>holder_page = requests.get(URL_HOLDER).content
holder_soup = BeautifulSoup(holder_page, &#39;html.parser&#39;)
holder_result = holder_soup.find(class_=&quot;mr-3&quot;).text.strip().replace(&#39; addresses&#39;, &#39;&#39;)</code></pre><h3 id="get-coin-price">Get coin price</h3>
<p>By using get method, extract the msq_usdt price from open api in DIGIFINEX.</p>
<pre><code>header = { &quot;symbol&quot;: &quot;msq_usdt&quot; }
price = requests.get(URL_PRICE, header).json()[&quot;ticker&quot;][0][&quot;last&quot;]</code></pre><p>And then, set multiplier to exchange currency. (Use api of currency exchange rate)</p>
<pre><code>currency = requests.get(URL_CURRENCY).json()[&quot;rates&quot;][&quot;KRW&quot;]
price_result = &quot;{:,}&quot;.format(round(price*currency, 2))</code></pre><h3 id="edit-original-html-file">Edit original html file</h3>
<p>Use find(class_=&quot;CLASS_NAME&quot;) method and index, replace the target value.</p>
<pre><code>with open(FILE_PATH) as inf:
  soup = BeautifulSoup(inf.read(), &#39;html.parser&#39;)
  index = 0
  for tag in soup.find(class_=&quot;section section-01&quot;).find(class_=&quot;statistics&quot;).find_all(class_=&quot;value&quot;):
    if index == 0:
      tag.string.replace_with(price_result + &quot; KRW&quot;)
    if index == 2:
      tag.string.replace_with(holder_result)
    index += 1
  new_txt = soup.prettify()</code></pre><p>Overwrite</p>
<pre><code>with open(FILE_PATH, &#39;w&#39;) as new_file:
  new_file.write(new_txt)</code></pre><h3 id="crontab-script">Crontab script</h3>
<p>It executes every 1 minute</p>
<pre><code>* * * * * /usr/bin/python3 /var/www/html/customer/globalmsq.com/public_html/scrap.py</code></pre><h3 id="references">References</h3>
<blockquote>
<p><a href="https://ostechnix.com/a-beginners-guide-to-cron-jobs/">https://ostechnix.com/a-beginners-guide-to-cron-jobs/</a>
<a href="https://towardsdatascience.com/how-to-schedule-python-scripts-with-cron-the-only-guide-youll-ever-need-deea2df63b4e">https://towardsdatascience.com/how-to-schedule-python-scripts-with-cron-the-only-guide-youll-ever-need-deea2df63b4e</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Instagram clone (Facebook login - OAuth)]]></title>
            <link>https://velog.io/@dev_sseop-e/Spring-Instagram-clone-Facebook-login-OAuth</link>
            <guid>https://velog.io/@dev_sseop-e/Spring-Instagram-clone-Facebook-login-OAuth</guid>
            <pubDate>Thu, 14 Apr 2022 02:10:48 GMT</pubDate>
            <description><![CDATA[<h3 id="oauth2detailsservice">OAuth2DetailsService</h3>
<p>Get facebook social username, email, name and process password -&gt; build</p>
<pre><code>public class OAuth2DetailsService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        OAuth2User oAuth2User = super.loadUser(userRequest);

        Map&lt;String, Object&gt; userInfo = oAuth2User.getAttributes();

        String username = &quot;facebook_&quot; + (String) userInfo.get(&quot;id&quot;);
        String password = new BCryptPasswordEncoder().encode(UUID.randomUUID().toString());
        String email = (String) userInfo.get(&quot;email&quot;);
        String name = (String) userInfo.get(&quot;name&quot;);

        User userEntity = userRepository.findByUsername(username);
        if(userEntity == null) {
            User user = User.builder()
                    .username(username)
                    .password(password)
                    .email(email)
                    .name(name)
                    .role(&quot;ROLE_USER&quot;)
                    .build();
            return new PrincipalDetails(userRepository.save(user), oAuth2User.getAttributes());
        } else {
            return new PrincipalDetails(userEntity, oAuth2User.getAttributes());
        }
    }

}
</code></pre><p>Make an overload</p>
<pre><code>public PrincipalDetails(User user, Map&lt;String, Object&gt; attributes) {
        this.user = user;
    }</code></pre><p>Catch the facebook login users to append at PrincipalDetails data.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Instagram clone (Comment, AOP, Bug fix)]]></title>
            <link>https://velog.io/@dev_sseop-e/Spring-Instagram-clone-Comment-bug-fix</link>
            <guid>https://velog.io/@dev_sseop-e/Spring-Instagram-clone-Comment-bug-fix</guid>
            <pubDate>Thu, 14 Apr 2022 00:43:31 GMT</pubDate>
            <description><![CDATA[<h3 id="model">Model</h3>
<p>userId, imageId, content(length limits)
Didn&#39;t use native query.</p>
<pre><code>public class Comment {

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

    @Column(length = 100, nullable = false)
    private String content;

    @JsonIgnoreProperties({&quot;images&quot;})
    @JoinColumn(name = &quot;userId&quot;)
    @ManyToOne(fetch = FetchType.EAGER)
    private User user;

    @JoinColumn(name = &quot;imageId&quot;)
    @ManyToOne(fetch = FetchType.EAGER)
    private Image image;

    private LocalDateTime createDate;

    @PrePersist
    public void createDate() {
        this.createDate = LocalDateTime.now();
    }

}</code></pre><h3 id="controller">Controller</h3>
<p>Call addCmt and deleteCmt from CommentService. BindingResult -&gt; AOP proccess</p>
<pre><code>public class CommentApiController {

    private final CommentService commentService;

    @PostMapping(&quot;/api/comment&quot;)
    public ResponseEntity&lt;?&gt; commentSave(@Valid @RequestBody CommentDto commentDto, BindingResult bindingResult, @AuthenticationPrincipal PrincipalDetails principalDetails) {
        Comment comment = commentService.addCmt(commentDto.getContent(), commentDto.getImageId(), principalDetails.getUser().getId());
        return new ResponseEntity&lt;&gt;(new CMRespDto&lt;&gt;(1, &quot;adding comment success&quot;, comment), HttpStatus.CREATED);
    }

    @DeleteMapping(&quot;/api/comment/{id}&quot;)
    public ResponseEntity&lt;?&gt; commentDelete(@PathVariable int id) {
        commentService.deleteCmt(id);
        return new ResponseEntity&lt;&gt;(new CMRespDto&lt;&gt;(1, &quot;deleting comment success&quot;, null), HttpStatus.OK);
    }

}v</code></pre><h3 id="service">Service</h3>
<p>Get the id from Image object and User object(for imageId and userId). When making object, insert id only. Therefore, it just returns image and user objects which have only id value. Set the comment by using setter(content, image, userEntity).</p>
<pre><code>public class CommentService {

    private final CommentRepository commentRepository;
    private final UserRepository userRepository;

    @Transactional
    public Comment addCmt(String content, int imageId, int userId) {

        Image image = new Image();
        image.setId(imageId);

        User userEntity = userRepository.findById(userId).orElseThrow(() -&gt; {
            throw new CustomApiException(&quot;Can&#39;t find user id&quot;);
        });

        Comment comment = new Comment();
        comment.setContent(content);
        comment.setImage(image);
        comment.setUser(userEntity);

        return commentRepository.save(comment);
    }

    @Transactional
    public void deleteCmt(int id) {
        try { // when it occurs error
            commentRepository.deleteById(id);
        } catch(Exception e) {
            throw new CustomApiException(e.getMessage());
        }
    }

}</code></pre><h3 id="aop">AOP</h3>
<p>proceedingJoinPoint: In apiController, can access to all target. apiAdvice pre execute than function in apiController.</p>
<pre><code>public class ValidationAdvice {

    @Around(&quot;execution(* com.cos.photogram.web.api.*Controller.*(..))&quot;)
    public Object apiAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        Object[] args = proceedingJoinPoint.getArgs();

        for (Object arg: args) {
            if(arg instanceof BindingResult) {
                BindingResult bindingResult = (BindingResult) arg;

                if (bindingResult.hasErrors()) {
                    Map&lt;String, String&gt; errorMap = new HashMap&lt;&gt;();
                    for (FieldError error : bindingResult.getFieldErrors()) {
                        errorMap.put(error.getField(), error.getDefaultMessage());
                    }
                    throw new CustomValidationApiException(&quot;Validation check failed&quot;, errorMap);
                }
            }
        }

        return proceedingJoinPoint.proceed(); // execute origin function
    }

    @Around(&quot;execution(* com.cos.photogram.web.*Controller.*(..))&quot;)
    public Object advice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        Object[] args = proceedingJoinPoint.getArgs();

        for (Object arg: args) {
            if(arg instanceof BindingResult) {
                BindingResult bindingResult = (BindingResult) arg;

                if (bindingResult.hasErrors()) { // pre-processing
                    Map&lt;String, String&gt; errorMap = new HashMap&lt;&gt;();
                    for (FieldError error : bindingResult.getFieldErrors()) {
                        errorMap.put(error.getField(), error.getDefaultMessage());
                    }
                    throw new CustomValidationException(&quot;Validation check failed&quot;, errorMap);
                }
            }
        }

        return proceedingJoinPoint.proceed();
    }

}</code></pre><h3 id="bug-fix">Bug fix</h3>
<h4 id="prob-like-button-infinite-recursion">Prob. Like button infinite recursion</h4>
<p>When like button is on active, it can&#39;t load the image.</p>
<h4 id="sol-jsonignoreproperties">Sol. JsonIgnoreProperties</h4>
<p>In Image domain, it imports likes and likes imports image again. Therefore, when imporing likes, add this code</p>
<pre><code>@JsonIgnoreProperties({&quot;image&quot;})</code></pre><p>Result:
<img src="https://velog.velcdn.com/images/dev_sseop-e/post/e9047e65-7234-471e-bb7f-91df5f75abb7/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Instagram clone (Feed, Likes)]]></title>
            <link>https://velog.io/@dev_sseop-e/220411-Spring-Instagram-clone-feed-likes</link>
            <guid>https://velog.io/@dev_sseop-e/220411-Spring-Instagram-clone-feed-likes</guid>
            <pubDate>Mon, 11 Apr 2022 07:19:19 GMT</pubDate>
            <description><![CDATA[<h3 id="feed">Feed</h3>
<p>In ImageRepository, add the native query to select images from following users. Get the image from toUserId and show in the fromUserId&#39;s feed.</p>
<pre><code>public interface ImageRepository extends JpaRepository&lt;Image, Integer&gt; {

    @Query(value = &quot;SELECT * FROM image WHERE userId IN (SELECT toUserId FROM subscribe WHERE fromUserId = :principalId) ORDER BY id DESC&quot;, nativeQuery = true)
    Page&lt;Image&gt; mStory(int principalId, Pageable pageable);

}</code></pre><p>In ImageService, import images from ImageRepository</p>
<pre><code>public Page&lt;Image&gt; imageStory(int principalId, Pageable pageable) {
    Page&lt;Image&gt; images = imageRepository.mStory(principalId, pageable);
    return images;
}</code></pre><p>Use ImageApiController and call the imageService. (There are 5 images in one page by default)</p>
<pre><code>public class ImageApiController {

    private final ImageService imageService;

    @GetMapping(&quot;/api/image&quot;)
    public ResponseEntity&lt;?&gt; imageStory(@AuthenticationPrincipal PrincipalDetails principalDetails,
                                        @PageableDefault(size = 5) Pageable pageable) {
        Page&lt;Image&gt; images = imageService.imageStory(principalDetails.getUser().getId(), pageable);
        return new ResponseEntity&lt;&gt;(new CMRespDto&lt;&gt;(1, &quot;Success&quot;, images), HttpStatus.OK);
    }

}</code></pre><h3 id="likes">Likes</h3>
<h4 id="model">Model</h4>
<p>Get imageId from Image class by using native query.
Below code is interface</p>
<pre><code>public interface LikesRepository extends JpaRepository&lt;Likes, Integer&gt; {

    @Modifying
    @Query(value = &quot;INSERT INTO likes(imageId, userId, createDate) VALUES(:imageId, :principalId, now())&quot;, nativeQuery = true)
    int mLikes(int imageId, int principalId);

    @Modifying
    @Query(value = &quot;DELETE FROM likes WHERE imageId = :imageId AND userId = :principalId&quot;, nativeQuery = true)
    int mUnLikes(int imageId, int principalId);

}</code></pre><h4 id="controller">Controller</h4>
<p>Make controller in ImageApiController. &#39;likes&#39;, &#39;unlikes&#39; and each mapping -&gt; PostMapping, DeleteMapping. add below</p>
<pre><code>@PostMapping(&quot;/api/image/{imageId}/likes&quot;)
public ResponseEntity&lt;?&gt; likes(@PathVariable int imageId, @AuthenticationPrincipal PrincipalDetails principalDetails) {
    likesService.like(imageId, principalDetails.getUser().getId());
    return new ResponseEntity&lt;&gt;(new CMRespDto&lt;&gt;(1, &quot;Like Success&quot;, null), HttpStatus.CREATED);
}

@DeleteMapping(&quot;/api/image/{imageId}/likes&quot;)
public ResponseEntity&lt;?&gt; unLikes(@PathVariable int imageId, @AuthenticationPrincipal PrincipalDetails principalDetails) {
    likesService.unLike(imageId, principalDetails.getUser().getId());
    return new ResponseEntity&lt;&gt;(new CMRespDto&lt;&gt;(1, &quot;Unlike success&quot;, null), HttpStatus.CREATED);
}</code></pre><h4 id="service">Service</h4>
<p>Get mLikes(imageId, principalId), mUnLikes(imageId, principalId) from LikesRepository.</p>
<h4 id="bug-fix">Bug fix</h4>
<p>In Likes model, import User -&gt; Images -&gt; User, Likes ...
Therefore, add under code to the private User in Likes.</p>
<pre><code>@JsonIgnoreProperties({&quot;images&quot;})</code></pre><p><img src="https://velog.velcdn.com/images/dev_sseop-e/post/2d30393f-f8d8-4c01-bebf-5be463302fd6/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Instagram clone (Image upload, Subscription)]]></title>
            <link>https://velog.io/@dev_sseop-e/220407-Spring-Instagram-clone-image-upload-subscription</link>
            <guid>https://velog.io/@dev_sseop-e/220407-Spring-Instagram-clone-image-upload-subscription</guid>
            <pubDate>Thu, 07 Apr 2022 02:47:37 GMT</pubDate>
            <description><![CDATA[<h3 id="image">Image</h3>
<h4 id="domain">Domain</h4>
<ol>
<li>user id</li>
<li>caption</li>
<li>post image url</li>
</ol>
<pre><code>public class Image {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String caption;
    private String postImageUrl; // Image will be saved as folder in server - insert path in DB

    @JoinColumn(name=&quot;userId&quot;)
    @ManyToOne
    private User user;

    private LocalDateTime createDate;

    @PrePersist
    public void createDate() {
        this.createDate = LocalDateTime.now();
    }

}</code></pre><p>Dto is used to build caption, postImageUrl, user.</p>
<h4 id="service">Service</h4>
<p>Get the file path from <em>application.yml</em>, image name is made by UUID_{originalFileName}.</p>
<p>Therefore, image file and path are saved to image table(imageRepository).</p>
<pre><code>@Transactional
public void imageUpload(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails) {

    UUID uuid = UUID.randomUUID();
    String imageFileName = uuid + &quot;_&quot; + imageUploadDto.getFile().getOriginalFilename();

    Path imageFilePath = Paths.get(uploadFolder + imageFileName);

    // Communication, I/O -&gt; can occur exception
    try {
        Files.write(imageFilePath, imageUploadDto.getFile().getBytes());
    } catch (Exception e) {
        e.printStackTrace();
    }

    // save to image table
    Image image = imageUploadDto.toEntity(imageFileName, principalDetails.getUser());
    imageRepository.save(image);

}</code></pre><p>In controller, throw exception if image is not found.</p>
<h3 id="subscription">Subscription</h3>
<h4 id="domain-1">Domain</h4>
<ol>
<li>user id</li>
<li>from user</li>
<li>to user</li>
</ol>
<pre><code>@Table(
        uniqueConstraints = {
                @UniqueConstraint(
                        name=&quot;subscribe_uk&quot;,
                        columnNames = {&quot;fromUserId&quot;, &quot;toUserId&quot;}
                )
        }
)
public class Subscribe {

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

    @JoinColumn(name=&quot;fromUserId&quot;)
    @ManyToOne
    private User fromUser;

    @JoinColumn(name=&quot;toUserId&quot;)
    @ManyToOne
    private User toUser;

    private LocalDateTime createDate;

    @PrePersist
    public void createDate() {
        this.createDate = LocalDateTime.now();
    }

}</code></pre><p>(&#39;fromUser&#39;, &#39;toUser&#39;) set is unique. (use @uniqueConstraints)</p>
<h4 id="service-1">Service</h4>
<p>First in SubscribeRepository, make &#39;create, delete set&#39; method. (by using native query)</p>
<pre><code>public interface SubscribeRepository extends JpaRepository&lt;Subscribe, Integer&gt; {

    @Modifying // for INSERT, DELETE, UPDATE in native query
    @Query(value = &quot;INSERT INTO subscribe(fromUserId,toUserId,createDate) VALUES(:fromUserId,:toUserId,now())&quot;, nativeQuery = true)
    void mSubscribe(int fromUserId, int toUserId); // 1(success, number of changed row), 0(not executed), -1(fail)

    @Modifying
    @Query(value = &quot;DELETE FROM subscribe WHERE fromUserId=:fromUserId AND toUserId=:toUserId&quot;, nativeQuery = true)
    void mUnSubscribe(int fromUserId, int toUserId); // 1, -1

}</code></pre><p>Back to service, access to repository and throw exception about any prob.</p>
<pre><code>public class SubscribeService {

    private final SubscribeRepository subscribeRepository;

    @Transactional
    public void sub(int fromUserId, int toUserId) {
        try {
            subscribeRepository.mSubscribe(fromUserId, toUserId);
        } catch(Exception e) {
            throw new CustomApiException(&quot;Already subscribed&quot;);
        }
    }

    @Transactional
    public void unSub(int fromUserId, int toUserId) {
        subscribeRepository.mUnSubscribe(fromUserId, toUserId);
    }

}</code></pre><h4 id="apicontroller">apiController</h4>
<p>Use ResponseEntity&lt;&gt; with PostMapping and DeleteMapping, call the service logic.</p>
<pre><code>public class SubscribeApiController {

    private final SubscribeService subscribeService;

    @PostMapping(&quot;/api/subscribe/{toUserId}&quot;)
    public ResponseEntity&lt;?&gt; subscribe(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable int toUserId) {
        subscribeService.sub(principalDetails.getUser().getId(), toUserId);
        return new ResponseEntity&lt;&gt;(new CMRespDto&lt;&gt;(1, &quot;Subscription success&quot;, null), HttpStatus.OK);
    }

    @DeleteMapping(&quot;/api/subscribe/{toUserId}&quot;)
    public ResponseEntity&lt;?&gt; unSubscribe(@AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable int toUserId) {
        subscribeService.unSub(principalDetails.getUser().getId(), toUserId);
        return new ResponseEntity&lt;&gt;(new CMRespDto&lt;&gt;(1, &quot;Unsubscription success&quot;, null), HttpStatus.OK);
    }

}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Instagram clone (Exception handler)]]></title>
            <link>https://velog.io/@dev_sseop-e/220405-Spring-Instagram-clone-Exception-handler</link>
            <guid>https://velog.io/@dev_sseop-e/220405-Spring-Instagram-clone-Exception-handler</guid>
            <pubDate>Tue, 05 Apr 2022 06:35:40 GMT</pubDate>
            <description><![CDATA[<h3 id="customvalidationapiexception">CustomValidationApiException</h3>
<p>In previous post, CustomValidationException catch the signup validation exception (username, password, email, name)</p>
<p>For the Edit user info, create CustomValidationApiException.</p>
<pre><code>public class CustomValidationApiException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private Map&lt;String, String&gt; errorMap;

    public CustomValidationApiException(String message, Map&lt;String, String&gt; errorMap) {
        super(message);
        this.errorMap = errorMap;
    }

    public CustomValidationApiException(String message) {
        super(message);
    }

    public Map&lt;String, String&gt; getErrorMap() {
        return errorMap;
    }

}</code></pre><h3 id="controllerexceptionhandler">ControllerExceptionHandler</h3>
<p>Rewrite to</p>
<pre><code>@ExceptionHandler(CustomValidationException.class)
public String validationException(CustomValidationException e) {
    return Script.back(e.getErrorMap().toString()); // return javascript
}

@ExceptionHandler(CustomValidationApiException.class)
public ResponseEntity&lt;CMRespDto&lt;?&gt;&gt; validationApiException(CustomValidationApiException e) {
    return new ResponseEntity&lt;&gt;(new CMRespDto&lt;&gt;(-1, e.getMessage(), e.getErrorMap()), HttpStatus.BAD_REQUEST); // return data
}</code></pre><blockquote>
<p>note) Comparison of CMRespDto and Script
    1. Response with client - Script
    2. Ajax communication - CMRespDto
    3. Android communication - CMRespDto</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Instagram clone (Signup, Login, Auth)]]></title>
            <link>https://velog.io/@dev_sseop-e/220404-Spring-Instagram-clone-codingSignup-Login-Auth</link>
            <guid>https://velog.io/@dev_sseop-e/220404-Spring-Instagram-clone-codingSignup-Login-Auth</guid>
            <pubDate>Mon, 04 Apr 2022 11:30:49 GMT</pubDate>
            <description><![CDATA[<h3 id="basic-setup">Basic setup</h3>
<p>IDE: Intellij
DB: Mariadb
Framework: Spring boot
Compile: Maven (will convert to Gradle)
Port: 3307
Views: jsp</p>
<p>Almost front-end settings are done.</p>
<h3 id="security-setting">Security setting</h3>
<pre><code>@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers(&quot;/&quot;, &quot;/user/**&quot;, &quot;/image/**&quot;, &quot;/subscribe/**&quot;, &quot;/comment/**&quot;, &quot;/api/**&quot;).authenticated()
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage(&quot;/auth/signin&quot;) // GET
                .loginProcessingUrl(&quot;/auth/signin&quot;) // POST -&gt; spring security proceed login
                .defaultSuccessUrl(&quot;/&quot;);
    }</code></pre><p>Routes to &quot;/&quot;, &quot;/user/<del>&quot;, &quot;/image/</del>&quot;, &quot;/subscribe/<del>&quot;, &quot;/comment/</del>&quot;, &quot;/api/~&quot; should be authenticated.
Default is login page. (GET request -&gt; view page, POST request -&gt; validation method)</p>
<h3 id="domainuser">Domain(User)</h3>
<pre><code>public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // Strategy of increasing number follow the DB
    private int id;

    @Column(unique = true)
    private String username;
    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String name;
    private String bio;
    private String gender;

    @Column(nullable = false)
    private String email;
    private String phone;
    private String website;

    private String profileImageUrl;
    private String role;

    private LocalDateTime createDate;

    @PrePersist
    public void createDate() {
        this.createDate = LocalDateTime.now();
    }

}</code></pre><p>User domain session</p>
<h3 id="auth">Auth</h3>
<p>Get UserDetail interface to PrincipalDetails -&gt; Override
Didn&#39;t consider about account expiration, lock, enable</p>
<pre><code>public class PrincipalDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    private User user;

    public PrincipalDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        Collection&lt;GrantedAuthority&gt; collector = new ArrayDeque&lt;&gt;();
        collector.add(() -&gt; {return user.getRole();});
        return collector;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true; // return user.getExpired();
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}</code></pre><pre><code>public class PrincipalDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    // 1. Password: auto check
    // 2. Return(o) -&gt; auto make UserDetails type to session
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User userEntity = userRepository.findByUsername(username);

        if (userEntity == null) {
            return null;
        } else {
            return new PrincipalDetails(userEntity);
        }

    }

}</code></pre><h3 id="controllers">Controllers</h3>
<h4 id="authcontroller">AuthController</h4>
<pre><code>public class AuthController {

    private final AuthService authService;

    @GetMapping(&quot;/auth/signin&quot;)
    public String signinForm() {
        return &quot;auth/signin&quot;;
    }

    @GetMapping(&quot;/auth/signup&quot;)
    public String signupForm() {
        return &quot;auth/signup&quot;;
    }

    @PostMapping(&quot;/auth/signup&quot;)
    public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            Map&lt;String, String&gt; errorMap = new HashMap&lt;&gt;();
            for (FieldError error : bindingResult.getFieldErrors()) {
                errorMap.put(error.getField(), error.getDefaultMessage());
            }
            throw new CustomValidationException(&quot;Validation check failed&quot;, errorMap);
        } else {
            User user = signupDto.toEntity();
            User userEntity = authService.signupService(user);
            System.out.println(userEntity);
            return &quot;auth/signin&quot;;
        }

    }

}</code></pre><h4 id="usercontroller">UserController</h4>
<pre><code>public class UserController {

    @GetMapping(&quot;/user/{id}&quot;)
    public String profile(@PathVariable int id) {
        return &quot;user/profile&quot;;
    }

    @GetMapping(&quot;/user/{id}/update&quot;)
    public String update(@PathVariable int id, @AuthenticationPrincipal PrincipalDetails principalDetails) {
        return &quot;user/update&quot;;
    }

}</code></pre><h4 id="userapicontroller-restcontroller">UserApiController (RestController)</h4>
<pre><code>public class UserApiController {

    private final UserService userService;

    @PutMapping(&quot;/api/user/{id}&quot;)
    public CMRespDto&lt;?&gt; update(@PathVariable int id, UserUpdateDto userUpdateDto, @AuthenticationPrincipal PrincipalDetails principalDetails) {
        User userEntity = userService.editUser(id, userUpdateDto.toEntity());
        principalDetails.setUser(userEntity);
        return new CMRespDto&lt;&gt;(1, &quot;User info edit successfully&quot;, userEntity);
    }

}</code></pre><h3 id="services">Services</h3>
<h4 id="authservice">AuthService</h4>
<pre><code>public class AuthService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Transactional // Write(Insert, Update, Delete)
    public User signupService(User user) {
        String rawPassword = user.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        user.setPassword(encPassword);
        user.setRole(&quot;ROLE_USER&quot;);
        User userEntity = userRepository.save(user);
        return userEntity;
    }

}</code></pre><p>Logic of user authentication (Signup)
By default, role is ROLE_USER</p>
<h4 id="userservice">UserService</h4>
<pre><code>public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Transactional
    public User editUser(int id, User user) {
        // 1. Persistence
        User userEntity = userRepository.findById(id).get();

        // 2. Edit object - Dirty checking(update finished)
        userEntity.setName(user.getName());

        String rawPassword = user.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);
        userEntity.setPassword(encPassword);

        userEntity.setBio(user.getBio());
        userEntity.setWebsite(user.getWebsite());
        userEntity.setPhone(user.getPhone());
        userEntity.setGender(user.getGender());

        return userEntity;
    }

}</code></pre><p>Edit user info</p>
<h3 id="exception-management">Exception management</h3>
<p>Catch RuntimeException (extended by CustomValidationException)</p>
<pre><code>public class CustomValidationException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private Map&lt;String, String&gt; errorMap;

    public CustomValidationException(String message, Map&lt;String, String&gt; errorMap) {
        super(message);
        this.errorMap = errorMap;
    }

    public Map&lt;String, String&gt; getErrorMap() {
        return errorMap;
    }

}</code></pre><h4 id="controllerexceptionhandler">ControllerExceptionHandler</h4>
<pre><code>public class ControllerExceptionHandler {

    @ExceptionHandler(CustomValidationException.class)
    public String validationException(CustomValidationException e) {
        // Comparison of CMRespDto, Script
        // 1. Script is better for response with client
        // 2. Ajax communication - CMRespDto
        // 3. Android communication - CMRespDto
        return Script.back(e.getErrorMap().toString());
    }

}</code></pre><p>Refer the comments</p>
<h3 id="packages">Packages</h3>
<p><img src="https://media.vlpt.us/images/dev_sseop-e/post/fb27a802-ab7e-436e-8b6c-aac76a449941/Screen%20Shot%202022-04-04%20at%2020.29.54.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Heroku setting]]></title>
            <link>https://velog.io/@dev_sseop-e/Heroku</link>
            <guid>https://velog.io/@dev_sseop-e/Heroku</guid>
            <pubDate>Wed, 30 Mar 2022 04:42:57 GMT</pubDate>
            <description><![CDATA[<h3 id="install">Install</h3>
<p>Download and log in to Heroku</p>
<pre><code>$ heroku login</code></pre><h3 id="create-a-new-git-repository">Create a new Git repository</h3>
<p>Initialize a git repository</p>
<pre><code>$ cd my-project
$ git init
$ heroku git:remote -a app-name</code></pre><h3 id="deploy-my-application">Deploy my application</h3>
<p>Commit the code to repository</p>
<pre><code>$ git add .
$ git commit -am &quot;commit-message&quot;
$ git push heroku main</code></pre><h3 id="existing-git-repository">Existing Git repository</h3>
<p>Simply add the heroku remote</p>
<pre><code>$ heroku git:remote -a todo-management-seop</code></pre><h3 id="source">Source</h3>
<p>Install guide</p>
<blockquote>
<p><a href="https://devcenter.heroku.com/articles/heroku-cli">https://devcenter.heroku.com/articles/heroku-cli</a>
<a href="https://dashboard.heroku.com/apps/todo-management-seop/deploy/heroku-git">https://dashboard.heroku.com/apps/todo-management-seop/deploy/heroku-git</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Enrollment Page]]></title>
            <link>https://velog.io/@dev_sseop-e/220303-Enrollment-Page</link>
            <guid>https://velog.io/@dev_sseop-e/220303-Enrollment-Page</guid>
            <pubDate>Thu, 03 Mar 2022 14:56:12 GMT</pubDate>
            <description><![CDATA[<h2 id="install-simple-calendar">Install simple-calendar</h2>
<p>add the code below to Gemfile</p>
<pre><code>gem &quot;simple_calendar&quot;, &quot;~&gt; 2.4&quot;</code></pre><p>start bundle</p>
<pre><code>$ bundle install</code></pre><h2 id="set-scoffild">Set scoffild</h2>
<pre><code>$ gem g scoffild Schedules tutor_id:integer start_time:datetime active:integer</code></pre><p>parameters - tutor_id, start_time, active(1: available, 2: unavailable)</p>
<h2 id="homehtmlerb">home.html.erb</h2>
<p>Set the home</p>
<pre><code>&lt;h1&gt;Enrollment Page&lt;/h1&gt;
&lt;p id=&quot;notice&quot; style=&quot;color: red&quot;&gt;&lt;%= notice %&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Input your class format&lt;/h3&gt;
&lt;form action=&quot;/calendar&quot; method=&quot;get&quot;&gt;
  Find your tutor (id): &lt;input type=&quot;string&quot; name=&quot;tutor_id&quot; placeholder=&quot;1 ~ 5&quot;/&gt;&lt;br&gt;
  Set the time (min): &lt;input type=&quot;string&quot; name=&quot;time&quot; placeholder=&quot;20 or 40&quot;/&gt;&lt;br&gt;
  &lt;input type=&quot;submit&quot;/&gt;
&lt;/form&gt;
&lt;br&gt;
&lt;h3&gt;For tutors&lt;/h3&gt;
&lt;form action=&quot;/tutor_schedules&quot; method=&quot;get&quot;&gt;
  Type your tutor id: &lt;input type=&quot;string&quot; name=&quot;tutor_id&quot; placeholder=&quot;1 ~ 5&quot;/&gt;&lt;br&gt;
  &lt;input type=&quot;submit&quot;/&gt;
&lt;/form&gt;
&lt;br&gt;
&lt;a href=&quot;/index&quot;&gt;Show total schedules&lt;/a&gt;</code></pre><h2 id="indexhtmlerb">index.html.erb</h2>
<p>Load total week-calendar</p>
<pre><code>&lt;h1&gt;Schedules&lt;/h1&gt;

&lt;%= link_to &#39;New Schedules&#39;, new_schedule_path %&gt;
&lt;a href=&#39;/home&#39;&gt; Back to home &lt;/a&gt;
&lt;br&gt;&lt;br&gt;

&lt;%= week_calendar events: @schedules do |date, schedules| %&gt;
  &lt;div style=&quot;font-weight: 700&quot;&gt;
  &lt;%= date %&gt;
  &lt;/div&gt;
  &lt;hr&gt;
  &lt;% schedules.each do |schedules| %&gt;
    &lt;div&gt;
      &lt;%= schedules.tutor_id %&gt; / 
      &lt;%= schedules.start_time.hour %&gt;:&lt;% if schedules.start_time.min == 0 %&gt;00
      &lt;% else %&gt;&lt;%= schedules.start_time.min %&gt;
      &lt;% end %&gt; / 
      &lt;span style=&quot;font-weight: 600&quot;&gt;
      &lt;% if schedules.active == 1 %&gt;
      Available
      &lt;% else %&gt;
      Unavailable
      &lt;% end %&gt;
    &lt;/div&gt;
  &lt;% end %&gt;
&lt;% end %&gt;</code></pre><h2 id="tutor_scheduleshtmlerb">tutor_schedules.html.erb</h2>
<p>Tutor-schedules</p>
<pre><code>&lt;h1&gt;Schedules&lt;/h1&gt;
&lt;%= link_to &#39;New Schedules&#39;, new_schedule_path %&gt;
&lt;a href=&#39;/home&#39;&gt; Back to home &lt;/a&gt;
&lt;br&gt;&lt;br&gt;

&lt;%= week_calendar events: @schedules do |date, schedules| %&gt;
  &lt;div style=&quot;font-weight: 700&quot;&gt;
  Date
  &lt;%= date %&gt;
  &lt;/div&gt;
  &lt;hr&gt;
  &lt;% schedules.each do |schedules| %&gt;
    &lt;div&gt;
      &lt;%= schedules.tutor_id %&gt;
      &lt;%= schedules.start_time.hour %&gt;:&lt;% if schedules.start_time.min == 0 %&gt;00
      &lt;% else %&gt;&lt;%= schedules.start_time.min %&gt;
      &lt;% end %&gt;
      &lt;% if schedules.active == 1 %&gt;
      Available
      &lt;% else %&gt;
      Unavailable
      &lt;% end %&gt;
      &lt;%= link_to &#39;Show&#39;, schedules %&gt;
    &lt;/div&gt;
  &lt;% end %&gt;
&lt;% end %&gt;</code></pre><h2 id="calendarhtmlerb">calendar.html.erb</h2>
<p>Enroll by tutor_id, class running time</p>
<pre><code>&lt;h1&gt;Schedules&lt;/h1&gt;

&lt;a href=&#39;/home&#39;&gt; Back to home &lt;/a&gt;&lt;br&gt;
&lt;br&gt;

&lt;%= week_calendar events: @schedules do |date, schedules| %&gt;
  &lt;div style=&quot;font-weight: 700&quot;&gt;
  Date
  &lt;%= date %&gt;
  &lt;/div&gt;
  &lt;hr&gt;

  &lt;% if @time == &quot;20&quot; %&gt;
    &lt;% schedules.each do |schedules| %&gt;
      &lt;div&gt;
        &lt;%= schedules.tutor_id %&gt;
        &lt;%= schedules.start_time.hour %&gt;:&lt;% if schedules.start_time.min == 0 %&gt;00
        &lt;% else %&gt;&lt;%= schedules.start_time.min %&gt;
        &lt;% end %&gt;
        &lt;% if schedules.active == 1 %&gt;
        &lt;span style=&quot;color: green; font-weight: 600&quot;&gt;Available&lt;/span&gt;&lt;br&gt;
        &lt;% else %&gt;
        &lt;span style=&quot;color: gray; font-weight: 600&quot;&gt;Unavailable&lt;/span&gt;&lt;br&gt;
        &lt;% end %&gt;
      &lt;/div&gt;
    &lt;% end %&gt;

  &lt;% elsif @time == &quot;40&quot; %&gt;
    &lt;% schedules.each do |schedules| %&gt;
      &lt;div&gt;
        &lt;%= schedules.tutor_id %&gt;
        &lt;%= schedules.start_time.hour %&gt;:&lt;% if schedules.start_time.min == 0 %&gt;00
        &lt;% else %&gt;&lt;%= schedules.start_time.min %&gt;
        &lt;% end %&gt;
        &lt;% if schedules.active == 1 %&gt;
          &lt;%= render :template =&gt; &quot;schedules/search_next&quot;, :locals =&gt; {:@next_time =&gt; schedules.start_time + 30*60, :@sheet =&gt; @schedules} %&gt;
        &lt;% elsif schedules.active == 2 %&gt;
          &lt;span style=&quot;color: gray; font-weight: 600&quot;&gt;Unavailable&lt;/span&gt;&lt;br&gt;
        &lt;% end %&gt;
      &lt;/div&gt;
    &lt;% end %&gt;
  &lt;% end %&gt;

&lt;% end %&gt;</code></pre><p>For 40 min class, pass the parameter of &#39;schedules.start_time + 30*60(sec)&#39; to search_next.html.erb</p>
<h2 id="search_nexthtmlerb">search_next.html.erb</h2>
<p>Search if next class is available</p>
<pre><code>&lt;% @sheet.each_with_index do |schedules, index| %&gt;
  &lt;% if schedules.start_time == @next_time %&gt;
    &lt;% if schedules.active == 1 %&gt;
      &lt;span style=&quot;color: green; font-weight: 600&quot;&gt;Available&lt;/span&gt;&lt;br&gt;
    &lt;% elsif schedules.active == 2%&gt;
      &lt;span style=&quot;color: gray; font-weight: 600&quot;&gt;Unavailable&lt;/span&gt;&lt;br&gt;
    &lt;% end %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;</code></pre><h2 id="schedules_controllerrb">schedules_controller.rb</h2>
<pre><code>class SchedulesController &lt; ApplicationController
  before_action :set_schedule, only: %i[ show edit update destroy ]

  # GET /schedules or /schedules.json
  def home
  end

  def calendar
    @id = params[:tutor_id]
    @time = params[:time]
    @schedules = Schedule.where(tutor_id: @id)
    if (@id.to_i&lt;1) || (@id.to_i&gt;5)
      redirect_to &#39;/home&#39;, notice: &quot;Tutor not found.&quot;
    elsif (@time.to_i != 20) &amp;&amp; (@time.to_i != 40)
      redirect_to &#39;/home&#39;, notice: &quot;Class duration not found.&quot;
    end
  end

  def tutor_schedules
    @id = params[:tutor_id]
    @schedules = Schedule.where(tutor_id: @id)
  end

  def index
    @schedules = Schedule.all
  end

  # GET /schedules/1 or /schedules/1.json
  def show
  end

  # GET /schedules/new
  def new
    @schedule = Schedule.new
  end

  # GET /schedules/1/edit
  def edit
  end

  # POST /schedules or /schedules.json
  def create
    @schedule = Schedule.new(schedule_params)

    respond_to do |format|
      if @schedule.save
        format.html { redirect_to schedule_url(@schedule), notice: &quot;Schedule was successfully created.&quot; }
        format.json { render :show, status: :created, location: @schedule }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @schedule.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /schedules/1 or /schedules/1.json
  def update
    respond_to do |format|
      if @schedule.update(schedule_params)
        format.html { redirect_to schedule_url(@schedule), notice: &quot;Schedule was successfully updated.&quot; }
        format.json { render :show, status: :ok, location: @schedule }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @schedule.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /schedules/1 or /schedules/1.json
  def destroy
    @schedule.destroy

    respond_to do |format|
      format.html { redirect_to schedules_url, notice: &quot;Schedule was successfully destroyed.&quot; }
      format.json { head :no_content }
    end
  end

  # DELETE all schedules
  def total_destroy
    @schedules = Schedule.all
    @schedules.each do |schedule|
      schedule.destroy
    end
    redirect_to &#39;/index&#39;
  end

  def import
    Schedule.import(params[:file])
    redirect_to schedules_path, notice: &quot;Schedules Added Successfully&quot;
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_schedule
      @schedule = Schedule.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def schedule_params
      params.require(:schedule).permit(:tutor_id, :start_time, :active)
    end
end</code></pre><h2 id="results">Results</h2>
<p>home page
<img src="https://images.velog.io/images/dev_sseop-e/post/737e9c53-0dd1-48c6-b3cb-fa6b326afa07/Screen%20Shot%202022-03-04%20at%200.16.04.png" alt="">
enroll class page
<img src="https://images.velog.io/images/dev_sseop-e/post/cb6e40dd-dfaf-47b9-ad52-b6547df2a451/Screen%20Shot%202022-03-04%20at%200.16.12.png" alt="">
total classes
<img src="https://images.velog.io/images/dev_sseop-e/post/f67e3db5-9ccd-4df8-9c99-177647060c8e/Screen%20Shot%202022-03-04%20at%200.16.20.png" alt=""></p>
<h3 id="references">References</h3>
<p>Ruby on Rails guide</p>
<blockquote>
<p><a href="https://rubykr.github.io/rails_guides/getting_started.html">https://rubykr.github.io/rails_guides/getting_started.html</a></p>
</blockquote>
<p>Install Ruby on Rails</p>
<blockquote>
<p><a href="https://ringle.notion.site/rails-mac-a9f4437721a74b19baf9c526b1823b9a">https://ringle.notion.site/rails-mac-a9f4437721a74b19baf9c526b1823b9a</a></p>
</blockquote>
<p>Implement simple-calendar</p>
<blockquote>
<p><a href="https://linuxtut.com/implement-a-reservation-system-using-rails-and-simple-calendar!-let%27s-add-validation-to-datetime!-2a900/">https://linuxtut.com/implement-a-reservation-system-using-rails-and-simple-calendar!-let%27s-add-validation-to-datetime!-2a900/</a></p>
</blockquote>
<p>Simple-calendar github origin guide</p>
<blockquote>
<p><a href="https://github.com/excid3/simple_calendar">https://github.com/excid3/simple_calendar</a></p>
</blockquote>
<p>Render with locals</p>
<blockquote>
<p><a href="https://apidock.com/rails/ActionController/Base/render">https://apidock.com/rails/ActionController/Base/render</a>
<a href="https://stackoverflow.com/questions/19907178/rails-partial-locals-not-working">https://stackoverflow.com/questions/19907178/rails-partial-locals-not-working</a></p>
</blockquote>
<p>Pass parameters in the &#39;a href&#39; tag</p>
<blockquote>
<p><a href="https://stackoverflow.com/questions/18276073/passing-params-to-href-in-rails">https://stackoverflow.com/questions/18276073/passing-params-to-href-in-rails</a></p>
</blockquote>
<p>Import CSV file</p>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=_NSBm_Q431Y">https://www.youtube.com/watch?v=_NSBm_Q431Y</a></p>
</blockquote>
<p>Ruby on Rails basic</p>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=iNrT0O2_MQM&amp;list=PLEBQPmkNcLCIE9ERi4k_nUkGgJoBizx6s">https://www.youtube.com/watch?v=iNrT0O2_MQM&amp;list=PLEBQPmkNcLCIE9ERi4k_nUkGgJoBizx6s</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Setup Ruby on Rails (with Troubleshooting)]]></title>
            <link>https://velog.io/@dev_sseop-e/220225</link>
            <guid>https://velog.io/@dev_sseop-e/220225</guid>
            <pubDate>Fri, 25 Feb 2022 15:36:41 GMT</pubDate>
            <description><![CDATA[<h3 id="prob-1-permission-denied-and-failed-to-install-bundle">Prob 1. Permission denied and failed to install bundle</h3>
<h4 id="sol">sol)</h4>
<p>install &#39;rvm&#39;</p>
<blockquote>
<p><a href="https://zwbetz.com/install-ruby-version-manager-on-mac/">https://zwbetz.com/install-ruby-version-manager-on-mac/</a></p>
</blockquote>
<p>run this command</p>
<pre><code>$ rvm fix-permissions</code></pre><h3 id="prob-2-rails-server-command-not-working">Prob 2. &#39;rails server&#39; command not working</h3>
<p>errMsg1:</p>
<blockquote>
<p>/Users/seop/.rvm/gems/ruby-3.0.0/gems/webpacker-5.4.3/lib/webpacker/configuration.rb:103:in `rescue in load&#39;: Webpacker configuration file not found /Users/seop/cdTask/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /Users/seop/cdTask/config/webpacker.yml (RuntimeError)</p>
</blockquote>
<p>errMsg2:</p>
<blockquote>
<p>/Users/seop/.rvm/rubies/ruby-3.0.0/lib/ruby/3.0.0/psych.rb:581:in `initialize&#39;: No such file or directory @ rb_sysopen - /Users/seop/cdTask/config/webpacker.yml (Errno::ENOENT)</p>
</blockquote>
<h4 id="sol-1">sol)</h4>
<p>run this command</p>
<pre><code>$ rails webpacker:install</code></pre><h3 id="prob-3-routing-error">Prob 3. Routing Error</h3>
<p>errMsg:</p>
<blockquote>
<p>No route matches [GET] &quot;/posts/ssg-ssr&quot;</p>
</blockquote>
<h4 id="sol-2">sol)</h4>
<p>open the link in the vscode terminal (not just type &#39;localhost:3000&#39;)</p>
<h3 id="result">Result</h3>
<p><img src="https://images.velog.io/images/dev_sseop-e/post/607879a8-dda8-4a95-8cdf-934369383c3a/Screen%20Shot%202022-02-26%20at%204.30.43%20PM.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Class vs Function]]></title>
            <link>https://velog.io/@dev_sseop-e/220222</link>
            <guid>https://velog.io/@dev_sseop-e/220222</guid>
            <pubDate>Tue, 22 Feb 2022 09:55:06 GMT</pubDate>
            <description><![CDATA[<p>React class vs function style</p>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=iY_vmP-Q3Ak&amp;list=PLuHgQVnccGMCEfBwnNGsJCQDiqSWI-edj">https://www.youtube.com/watch?v=iY_vmP-Q3Ak&amp;list=PLuHgQVnccGMCEfBwnNGsJCQDiqSWI-edj</a></p>
</blockquote>
<p>useReducer vs Redux</p>
<blockquote>
<p><a href="https://www.imaginarycloud.com/blog/react-hooks-vs-redux/">https://www.imaginarycloud.com/blog/react-hooks-vs-redux/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux]]></title>
            <link>https://velog.io/@dev_sseop-e/220217</link>
            <guid>https://velog.io/@dev_sseop-e/220217</guid>
            <pubDate>Tue, 22 Feb 2022 09:53:48 GMT</pubDate>
            <description><![CDATA[<p>Redux</p>
<blockquote>
<p><a href="https://redux.js.org/introduction/core-concepts">https://redux.js.org/introduction/core-concepts</a></p>
</blockquote>
<p>custom myLocationButton and zoomButtons</p>
<blockquote>
<p><a href="https://github.com/react-native-maps/react-native-maps/issues/1874">https://github.com/react-native-maps/react-native-maps/issues/1874</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Function, Class]]></title>
            <link>https://velog.io/@dev_sseop-e/220216</link>
            <guid>https://velog.io/@dev_sseop-e/220216</guid>
            <pubDate>Thu, 17 Feb 2022 14:11:50 GMT</pubDate>
            <description><![CDATA[<p>React-native-maps basic</p>
<blockquote>
<p><a href="https://blog.logrocket.com/react-native-maps-introduction/">https://blog.logrocket.com/react-native-maps-introduction/</a></p>
</blockquote>
<p>Functional components VS Class components</p>
<blockquote>
<p><a href="https://www.twilio.com/blog/react-choose-functional-components">https://www.twilio.com/blog/react-choose-functional-components</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[My location button]]></title>
            <link>https://velog.io/@dev_sseop-e/220215</link>
            <guid>https://velog.io/@dev_sseop-e/220215</guid>
            <pubDate>Thu, 17 Feb 2022 14:10:18 GMT</pubDate>
            <description><![CDATA[<p>My location button loading</p>
<pre><code class="language-javascript">  await Location.getCurrentPositionAsync({
    accuracy:
      Platform.OS === &quot;ios&quot;
        ? Location.Accuracy.Balanced
        : Location.Accuracy.Low,
  })</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[My location]]></title>
            <link>https://velog.io/@dev_sseop-e/220212</link>
            <guid>https://velog.io/@dev_sseop-e/220212</guid>
            <pubDate>Sun, 13 Feb 2022 20:07:33 GMT</pubDate>
            <description><![CDATA[<p>My Location</p>
<blockquote>
<p><a href="https://docs.expo.dev/versions/latest/sdk/location/#enabling-emulator-location">https://docs.expo.dev/versions/latest/sdk/location/#enabling-emulator-location</a>
<a href="https://snack.expo.dev">https://snack.expo.dev</a></p>
</blockquote>
<blockquote>
<p><a href="https://ebbnflow.tistory.com/268">https://ebbnflow.tistory.com/268</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sliding up panel]]></title>
            <link>https://velog.io/@dev_sseop-e/220209</link>
            <guid>https://velog.io/@dev_sseop-e/220209</guid>
            <pubDate>Sat, 12 Feb 2022 03:17:06 GMT</pubDate>
            <description><![CDATA[<p>Sliding up panel</p>
<blockquote>
<p><a href="https://www.npmjs.com/package/rn-sliding-up-panel">https://www.npmjs.com/package/rn-sliding-up-panel</a>
<a href="https://github.com/octopitus/rn-sliding-up-panel/blob/master/README.md">https://github.com/octopitus/rn-sliding-up-panel/blob/master/README.md</a></p>
</blockquote>
<blockquote>
<p>import SlidingUpPanel from &#39;rn-sliding-up-panel&#39;</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-native-maps components + Kakao search api]]></title>
            <link>https://velog.io/@dev_sseop-e/220207</link>
            <guid>https://velog.io/@dev_sseop-e/220207</guid>
            <pubDate>Mon, 07 Feb 2022 17:12:34 GMT</pubDate>
            <description><![CDATA[<p>React-native-maps components</p>
<blockquote>
<p><a href="https://github.com/react-native-maps/react-native-maps">https://github.com/react-native-maps/react-native-maps</a></p>
</blockquote>
<p>Marker</p>
<blockquote>
<p>import { Marker } from &#39;react-native-maps&#39;;</p>
</blockquote>
<blockquote>
<p><a href="https://medium.com/geekculture/mapview-in-expo-react-native-5aa69eb81519">https://medium.com/geekculture/mapview-in-expo-react-native-5aa69eb81519</a></p>
</blockquote>
<p>Kakao search api</p>
<blockquote>
<p><a href="https://m.blog.naver.com/kiddwannabe/221812712712">https://m.blog.naver.com/kiddwannabe/221812712712</a>
<a href="https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-keyword">https://developers.kakao.com/docs/latest/ko/local/dev-guide#search-by-keyword</a></p>
</blockquote>
<p>useState useEffect</p>
<blockquote>
<p><a href="https://xiubindev.tistory.com/97">https://xiubindev.tistory.com/97</a></p>
</blockquote>
<p>git command</p>
<blockquote>
<p><a href="https://github.com/joshnh/Git-Commands">https://github.com/joshnh/Git-Commands</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Naver-maps api]]></title>
            <link>https://velog.io/@dev_sseop-e/220127</link>
            <guid>https://velog.io/@dev_sseop-e/220127</guid>
            <pubDate>Sun, 06 Feb 2022 18:40:24 GMT</pubDate>
            <description><![CDATA[<p><a href="https://github.com/QuadFlask/react-native-naver-map">https://github.com/QuadFlask/react-native-naver-map</a></p>
<p>React-native naver-map</p>
<blockquote>
<p>Android - can&#39;t run emulator
iOS - some bugs</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-native Google map api]]></title>
            <link>https://velog.io/@dev_sseop-e/220126-%EA%B5%AC%EA%B8%80%EB%A7%B5-api</link>
            <guid>https://velog.io/@dev_sseop-e/220126-%EA%B5%AC%EA%B8%80%EB%A7%B5-api</guid>
            <pubDate>Wed, 26 Jan 2022 17:05:51 GMT</pubDate>
            <description><![CDATA[<p><a href="https://dev-yakuza.posstree.com/ko/react-native/react-native-maps/">https://dev-yakuza.posstree.com/ko/react-native/react-native-maps/</a>
<a href="https://github.com/dev-yakuza/react-native-map-example">https://github.com/dev-yakuza/react-native-map-example</a>
<a href="https://mildb.tistory.com/68">https://mildb.tistory.com/68</a></p>
]]></description>
        </item>
    </channel>
</rss>