반응형

 

#Firebase 를 대처할만한 편리하고 저렴하게 사용할 수 있는 backend solution인 supabase 소개는 따로.!

 

Supabase에서 Database를 생성하고 나서, database 를 사용하는 방법은 다음과 같습니다.

 

supabase guide 를 보면 쉽게 따라하실수 있습니다.

https://supabase.com/docs/reference/dart/upgrade-guide?queryGroups=version&version=2.0x

 

Flutter: Upgrade guide | Supabase Docs

Initialization does not await for session refresh In v1, Supabase.initialize() would await for the session to be refreshed before returning. This caused delays in the app's launch time, especially when the app is opened in a poor network environment. In v2

supabase.com

 

 

 

지금 하고 싶은 이야기는 이미 supabase를 사용하고 계시는 분들에게 유용한 기능인데요.

 

# database 컬럼으로 json 활용

database를 만들어 사용하다 보면, 데이터중에  json 으로 데이터를 넣어야 하는 경우가 있습니다.

 

 

그런데, json 으로 데이터 컬럼을 추가 할경우, 쿼리 할때 데이터를 가져온다음에 name 이나 type을 비교해야 하는 상당히 불편한 부분이 생깁니다.

다행히도 supabase의 eq() 함수는 json data 내부의 필드값을 접근 할 수 있는 기능을 제공합니다.

https://supabase.com/docs/reference/dart/using-filters

final data = await supabase
  .from('test')
  .select()
  .eq('data->type', 'hello');

 

 

 

# eq 의 -> 가 에러가 발생

그런데 안타깝게도 아래와 같은 에러가 발생 할 수 있습니다.

 

PostgrestException(message: invalid input syntax for type json, code: 22P02, details: The input string ended unexpectedly., hint: null)

 

 

아 !,  -> 기능으로 json 내부 접근해서 쿼리 할수 있을 줄 알았는데 동작을 안하네??? 이러고 좌절 하실수 있는데요.

걱정하지 마세요 !

 

제가 찾아본 자료에서는 json 내부에 접근하는 접근자  몇가지 있는데요.

가이드에 나와있는 접근자로는 오류가 발생하고 다른 접근자를 사용하면 됩니다.

가이드에 적힌 '->' json형태의 값을 넘기는 것이고, '->>'를 사용하면 text 값을 정상적으로 처리할 수 있습니다.

 

final data = await supabase
  .from('test')
  .select()
  .eq('data->>type', 'hello');

 

이렇게 사용하면 정상적으로 eq 가 동작합니다.

 

 

#json, jsonb 의 차이 및 접근자에 대해서 더 알고 싶다면

 

OperatorRight Operand TypeDescriptionExampleExample Result

-> int Get JSON array element (indexed from zero, negative integers count from the end) '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2 {"c":"baz"}
-> text Get JSON object field by key '{"a": {"b":"foo"}}'::json->'a' {"b":"foo"}
->> int Get JSON array element as text '[1,2,3]'::json->>2 3
->> text Get JSON object field as text '{"a":1,"b":2}'::json->>'b' 2
#> text[] Get JSON object at specified path '{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}' {"c": "foo"}
#>> text[] Get JSON object at specified path as text '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' 3

https://www.postgresql.org/docs/9.5/functions-json.html

 

 

[참고]

https://yeongunheo.tistory.com/entry/PostgreSQL-json-jsonb-%ED%83%80%EC%9E%85%EA%B3%BC-%EC%97%B0%EC%82%B0%EC%9E%90

 

[PostgreSQL] json, jsonb 타입과 연산자

데이터베이스 테이블 내 하나의 컬럼에 JSON 데이터를 저장하는 경우가 있습니다. 보통 외부에서 제공된 데이터를 별도의 처리 없이 그대로 저장할 때 JSON 타입으로 저장하게 됩니다. PostgreSQL은 J

yeongunheo.tistory.com

 

 

#해피코딩

반응형

 

JSON은 상당히 편리하면서 직관적인 데이터 형식이죠.

그래서 사람들이 많이 사용합니다.

 

코딩을 하다보면, json을 그대로 사용하기 보다는 보통 필요한 데이터 형을 class로 만들고 이를 json으로 encoding/decoding 하는 방식으로 많이 사용합니다.

 

그런데  매번 class 만들고 fromJson(), toJson()을 만들어서 사용하는것이 상당히 번거롭고, 실수할 때가 많습니다.

 

그래서 fromJson, toJson을 구현해주는 모듈들이 있죠.

대표적으로 json_serializable 이 있죠.

이를 활용하면 쉽게 json 변환이 가능한 data class를 정의 할 수 있습니다.

 

저는 요즘 freezed 라는 모듈을 주로 사용합니다.

 

freezed 2.5.7

https://pub.dev/packages/freezed

 

freezed | Dart package

Code generation for immutable classes that has a simple syntax/API without compromising on the features.

pub.dev

 

이 freezed 는 json_serialiazble 을 이용하여 data class를 구현해주는 모듈인데요. 제가 이를 사용하게 된 계기는 deep copy 때문이었습니다.

 

freezed는 fromJson, toJson 뿐만 아니라 copyWith() 함수를 자동으로 구현 해줍니다. 그리고 copyWith() 를 수행할때 멤버들의  copyWith()를 수행해서 전체가 복사가 됩니다.

이것이 하나의 큰 장점이고,

freezed 로 선언된 data class는 내부 데이터를 변경할 수 없습니다.

변경하려면, copyWith()로 특정 데이터만 변경해서 복사해서 사용해야 합니다.

그렇지 않고 변경가능한 class를 만들고 싶다면, @unfreezed를 이용합니다.

 

 

주의점!

간혹  freezed 로 정의한 class의 멤버중에 freezed로 되어있지 않은 class들을 데이터로 포함할 경우가 있습니다.

그런데 간혹, toJson() 이 정상적으로 동작하지 않을때도 있는데요.

이럴때 toJson()이 불리도록 explicitToJson  설정이 필요할때가 있습니다.

@unfreezed
class MyDataModel with _$MyDataModelModel {
  @JsonSerializable(explicitToJson: true)

  factory MyDataModel({
    required int id,
    required String name,
    DateTime? created_at,
    DateTime? updated_at,
    required Map<String, Health> data,
    bool? is_demo,
  })= _MyDataModel;

예를 들면 위와 같이 클래스를 정의한 경우, Health라는 class에 fromJson, toJson 을 구현 해놨다 하더라도, MyDataModel의 toJson()을 호출할때 Health의 toJson()이 호출되지 않습니다.

이유는 자동으로 생성된 코드에 Health의 toJson()을 호출하는 코드가 생성되지 않기 때문인데요.

이런 경우, toJson()이 불리도록,   @JsonSerializable(explicitToJson: true) 을 class 밑에 선언해 줍니다.

 

#해피코딩

반응형

 

Nivo 

https://nivo.rocks/components/

 

Components | nivo

 

nivo.rocks

 

 

 

 

Google Fonts

https://fonts.google.com/

 

Browse Fonts - Google Fonts

Making the web more beautiful, fast, and open through great typography

fonts.google.com

 

'Node.js , React, Docker' 카테고리의 다른 글

[AWS] 여러가지 알아야 할것들  (3) 2024.07.12
Docker 간단히 이해하기  (0) 2024.07.11
REST API  (0) 2017.06.20
react 시작하기!!  (0) 2016.12.29
반응형

 

우리는 종종  SDK 가 업데이트 되고 나서 원인 모를 빌드 에러가 발생하는 것을 경험하게 됩니다.

 

SDK가 매번 업데이트 되고, 자동으로 패키지들을 설치해줘서 개발 환경들은 점점 좋아지고 있습니다.

그러나 종종 프로젝트를 만든 시점이 오래되어 최신 SDK에서 프로젝트가 만들어졌을 시점의 환경을 더이상 지원하지 않거나 변경되는 경우가 있죠.

 

다행스럽게도 구글링을 해보면 비슷한 문제를 겪는 사람들을 볼수 있고 그들이 포스팅 해놓은 해결책으로 많은 부분을 해결할 수 있습니다.

 

하지만 종종 전혀 해결이 안되는 문제들이 발생할 경우들이 있죠.

이럴때는 정말  project를 완전히 지우고 새 프로잭트를 만들고 소스코드를 카피해서 사용하고 싶은 생각이 들때도 있습니다.

 

 

일단 기초적인 사례부터 정리해보도록 하겠습니다.

 

디버그 모드로 빌드 잘되는데 릴리즈로 이상하게 잘 안된다.

뭔가 app/build.gradle 에서 에러가 발생한다.

 

1. Signing Key

먼저 확인해보실것이 , release 에 사용하는 signing key 입니다.

종종  signing 키가 삭제되거나 signing key  관련 설정이 잘 안되어 있어서 build error 가 이상한 곳에서 발생할때가 있습니다.

 

2. SDK version 확인

compile sdk나 target sdk 설정이 잘못되어있는 경우에 많이 에러가 발생합니다.

또, 현재 개발 환경에 sdk version이 정상적으로 설치 되었는지 확인 하는 것도 필요합니다.

예를 들면 아래와 같이 SDK build tool 35 버전을 잘 설치가 되어있습니다.

 

 

 

그런데. 빌드할때 이상하게 낮은 버전으로 빌드를 하려고 하고 에러가 발생한다면, SDK 설치가 좀 덜되어있을 가능성이 있습니다.

flutter build 시 현재 최신 버전으로 했을때 android sdk version 34를 사용합니다.

그런데 현재 PC에 35, 30.3 만 download 되어있다면, 아마 낮은버전인 30.3으로 빌드를 하게 될것입니다.

현재 빌드환경이 어떤 버전으로 돌아가고 있는지 확인하고 Show Package Details로 해당 버전이 설치되어있는지 확인할 필요가 있습니다.

 

 

 

3. 드물지만, subproject build 할때 sdk 버전이 안먹히는 경우

아래와 같이 afterEvaluate 를 설정해서 트라이 해볼 수 있습니다.

afterEvaluate {
        android {
            compileSdkVersion 34
        }
    }

 

전체 코드

android/build.gradle 파일

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.buildDir = "../build"
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
    // Add this
    afterEvaluate {
        android {
            compileSdkVersion 34
        }
    }
    // End
}
subprojects {
    project.evaluationDependsOn(":app")
}

tasks.register("clean", Delete) {
    delete rootProject.buildDir
}

 

 

#해피코딩 !!

반응형


github의 action 을 사용할때 , 환경변수등을 설정할 필요가 있습니다.

이때 활용 할 수 있는 것이 이와 같습니다.

특히 android release 빌드를 할때는 keystore 가 반드시 필요한데, 이 keystore는 외부에 노출하면 안되는 것이라 참 난감합니다.

그런데 이를 지원해줄 수 있는 github의 secret variable 저장 기능이 있습니다.

이를 한번 알아봅시다.!

 

 

유사한 내용이지만 둘다 모두 필요한 내용이 있어서 정리를 위해 링크 남깁니다.

 


https://medium.com/@dcostalloyd90/automating-android-builds-with-github-actions-a-step-by-step-guide-2a02a54f59cd

 

 

Automating Android Builds with GitHub Actions: A Step-by-Step Guide

In this tutorial, we’ll walk you through the process of automating Android builds using GitHub Actions. This will streamline your…

medium.com

 

 

https://www.droidcon.com/2023/09/08/build-sign-and-create-release-build-using-github-actions/

 

 

위 내용들이 mac에서 셋업을 하면서 보니까. 약간 차이점들이 있었습니다. 그리고 오류도 좀 있고요.

저도 순서를 하나씩 짚어보면서 정리해보도록 하겠습니다.

 

1. github 에서 action 과 과련한 secret  파일들을 설정할 수 있는 부분이 있습니다.

github/[내프로젝트]/settings 에서 Secrets and variables 메뉴가 있습니다.

 

 

 

 

Secrets에 secret들을 하나씩 추가해봅시다.

 

 

KEYSTORE_BASE_64

RELEASE_KEYSTORE_ALIAS

RELEASE_KEYSTORE_PASSWORD

RELEASE_KEY_PASSWORD

 

이렇게 4가지를 차례대로 만들 예정입니다.

이미 키가 있다는 전제하에 설명합니다.(키 만드는 것은 여기서 확인!)

 

KEYSTORE_BASE_64 

사용하고 있는 key file이 있으실텐데 해당 key file을 사용하시면 됩니다.

저는 여기서 key.jks 라는 이름의 파일 이라고 가정하고 진행합니다.

base64 인코딩 하기

github의 secret 에는 파일을 추가 할 수 없습니다. 따라서 text를 이용해야 합니다. 때문에 key 파일을 문자열로 변환하기 위해 base64 인코딩합니다.

 

형식 : base64 -i <in-file> -o <outfile>

 

$ base64 -i key.jks -o keystore-b64.txt

 

이렇게 keystore-b64.txt를 열어서 해당 text를 KEYSTORE_BASE_64 에 저장합니다.

 

나머지  RELEASE_KEYSTORE_ALIAS / RELEASE_KEYSTORE_PASSWORD / RELEASE_KEY_PASSWORD

들은 키를 만들때 사용했던 alias, keystore passowrd, key passoword 를 적어주면 됩니다.

 

 

 

2. build.gradle 을 수정합니다.

이제 gradle 파일을 수정하러 가야 합니다.

android/app/build.gradle 

    signingConfigs{
        releaseWithSignedKey {
            
            storeFile file("../keystore.jks")
            storePassword System.getenv("RELEASE_KEYSTORE_PASSWORD")
            keyAlias System.getenv("RELEASE_KEYSTORE_ALIAS")
            keyPassword System.getenv("RELEASE_KEY_PASSWORD")
        }
    }
    
    :
    
    buildTypes {
        release {

            signingConfig = signingConfigs.releaseWithSignedKey
            System.out.println("signingConfig:$signingConfig")
        }
    }

 

signingConfigs 를 추가하고 buildType의 release에서 이 정보를 이용하도록 설정합니다.

 

자! 코드를 보셔서 아시겠지만, 로컬에서 release로 빌드를 하려면 어떻게 해야 할까요?

환경변수에 RELEASE_KEYSTORE_ALIAS,RELEASE_KEYSTORE_PASSWORD,RELEASE_KEY_PASSWORD 를 설정 해야 겠죠.

이건 쉬우니까 구글링 해서 찾어보시면 됩니다.

 

 

3. github action 의 workflow 수정 하기.

github action에서 핵심은 base64로 encoding 된 key를 다시 keystore.jks로 변환하여 사용하는 것과 환경 변수 세팅입니다.

 

    steps:
     - name: Checking out branch
       uses: actions/checkout@v3
     - name: Setup Java
       uses: actions/setup-java@v3
       with:
         distribution: 'zulu' # See 'Supported distributions' for available options
         java-version: '17'
         cache: 'gradle'
     - name: Decode Keystore
       env:
          ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
          RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
          RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
          RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} 

       run: |
          echo $ENCODED_STRING > keystore-b64.txt
          base64 -d -o keystore.jks < keystore-b64.txt
          
     - name: Setup flutter
       uses: subosito/flutter-action@v2
       with:
         flutter-version: '3.24.1'
     - run: flutter pub get

     - run: flutter build apk --debug

 

 

위와 같이 Decode Keystore를  actions/setup-java 다음에 추가해줍니다.

바로 아래 항목입니다.

     - name: Decode Keystore
       env:
          ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
          RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
          RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
          RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} 

       run: |
          echo $ENCODED_STRING > keystore-b64.txt
          base64 -d -o keystore.jks < keystore-b64.txt

 

여기까지 해주면 action 에서 release build 테스트를 위한 환경이 끝났습니다.

 

깃 커밋하시면 자동으로 릴리즈 빌드를 시험해보실 수 있습니다.

 

#해피코딩

반응형

 

AI 스타트업들에게: 곡괭이와 삽을 팔지 말고, 직접 캐세요

(foundercollective.medium.com)
12P by xguru 6일전 | favorite | 댓글 4개
  • AI가 모든 것을 변화시킬 것이라고 스타트업 업계에서는 믿고 있고, 새로운 파운데이션 모델의 급속한 발전과 놀라운 데모는 흥미로움
  • 하지만 새로운 기술 개발 속도, 법적 문제에 대한 불명확성, 모델의 일반적인 접근성, AI가 제기하는 고유한 상용화 과제로 인해 창업자들은 이 새로운 환경에서 어떻게 스타트업을 구축해야 할지 확신하지 못하고 있음

과거의 스타트업 전략이 새로운 AI 시대에 적용되기 어려운 이유

  • 많은 창업자들은 최신 기술 골드러시를 위한 도구를 구축하는 것이 승리하는 전략이라고 생각함
  • 이전 기술 물결에서는 다른 사람들이 최종 제품을 구축하는 데 도움이 되는 도구를 만드는 것이 안전하고 현명한 전략이었음
  • 하지만 이번에는 다르며, 지난 15년 동안 스타트업에 잘 작동했던 플레이북은 2024년 이후에는 성공하는 데 도움이 되지 않을 것임

SaaS는 솔루션 대신 도구를 판매하는 것

  • 지난 15년 동안의 B2B 소프트웨어 개발은 새로운 도구의 끊임없는 개발로 특징지어짐
  • 이는 대부분 마케터, CS 관리자, HR 담당자 및 기타 직원들이 인사이트를 얻고, 워크플로를 개선하며, 내부 및 외부 커뮤니케이션을 간소화하는 데 도움이 되는 포인트 솔루션이었음
  • 도구를 만드는 사람들조차도 새로운 도구가 있었음
  • Airtable 및 Shopify와 같은 수평적 로우 코드 앱은 중소기업과 사무직 근로자를 의사 엔지니어로 만들었음
  • 그 밑에는 앱 간 상호 연결을 쉽게 구축할 수 있는 Plaid 및 Zapier와 같은 또 다른 "도구와 도끼" 계층이 있었음
  • 또한 클라우드 제공업체의 부상으로 호스팅이 상품화되었음

AI 시대에는 솔루션을 직접 제공하라

  • 많은 창업자들이 AI 시대에 SaaS 플레이북을 복사하려고 하고 있음
  • 하지만 이들은 풀스택 비즈니스나 새로운 세대의 기술 지원 서비스에 대한 사고 방식을 재구성하는 것을 고려해야 함
  • 직원들을 위한 새로운 AI 기반 회계 소프트웨어 패키지를 기업에 판매하는 대신, 재무제표 소화, 의심스러운 항목 감사, 상세한 재무 보고서 작성을 위해 AI 도구를 핵심으로 하는 처음부터 구축된 회계법인을 상상해 볼 것
  • 또는 동일한 방식으로 새로운 로펌, 부동산 중개업체, 컨설팅 회사 같은 것을 상상해 볼 것
  • 수년 전 로펌을 혁신하려고 했던 Atrium은 단순히 너무 이른 시기였을 수 있음

기존 기업도 AI를 채택하지 않을까?

  • 이론적으로 기존 기업은 AI 도구를 도입하고 활용할 수 있음
  • 하지만 기존 조직이 근본적인 수준에서 재구성하는 데는 많은 노력이 필요함
  • AI가 침투함에 따라 많은 직원들이 자신의 책임을 방어하고 직업을 보호하려 할 것임
  • 채택 속도는 예상보다 느릴 수 있음
  • 이는 핵심에 AI를 두고 비즈니스를 설정하고 뒤처진 기업과 경쟁할 수 있는 기회를 만듦

AI 기반 풀스택 스타트업을 구축해야 하는 이유

  • 기존 기업이 AI의 잠재력을 완전히 채택하고 이해하는 것을 주저하는 것은 스타트업이 번성할 수 있는 기회임
  • 새로운 딜로이트나 맥킨지를 만드는 것은 쉽지 않음
  • 하지만 중소 규모 서비스 기업의 많은 고객은 이러한 브랜드 자본이 필요하지 않거나 감당할 수 없으며, 이는 스타트업에게 충분한 쐐기(Wedge)가 될 수 있음
  • 이런 고객에게 서비스를 제공하면, 시간이 지남에 따라 기존 기업의 신뢰와 브랜드 인지도 수준에 도달할 수 있는 새로운 세대의 서비스 비즈니스 기회를 창출할 수 있음

VC는 Tech-Enabled 서비스를 싫어하지 않나?

  • 역사적으로 풀스택 서비스 지향적 접근 방식은 창업자들 사이에서 인기가 없었음
  • VC는 Tech-Enabled 서비스 비즈니스에 투자하는 것을 좋아하지 않는다고 정확하게 믿음
  • 풀스택 솔루션은 실행하기 어렵고 마진이 낮으며 순수 소프트웨어 대신 더 많은 사람이 필요함
    • 하지만 AI가 그렇게 말하는 게임 체인저 기술이라고 믿는다면 이러한 반대는 더 이상 유효하지 않을 수 있음
    • 이전보다 훨씬 적은 인력만 필요할 수 있으므로 마진은 크게 개선될 것
    • 풀스택 솔루션은 위험한 베팅이지만 혼잡한 시장에서 501번째 삽을 팔려고 하는 것보다 더 위험하지 않음
    • 또 다른 도구가 되는 것은 이 새로운 세상에서 훌륭한 해자가 아님
    • AI를 처음부터 활용하여 일을 수행하는 새로운 엔드 투 엔드 솔루션을 구축해야 방어력을 확보할 수 있음
  • 물론 새로운 소프트웨어 회사를 위한 시장은 항상 존재함
    • 다만 창업자들이 브라우저 외의 대안을 고려해 보라는 제안을 드리고 싶을 뿐
    • 어떤 일을 하든 스타트업을 중심으로 생태계가 형성될 때까지 기다리지 말아야 함. 그런 일은 일어나지 않음
    • 대신 삽질을 시작할 것. 운이 좋으면 금을 발견할 수도 있음

 

 

원문

https://news.hada.io/topic?id=16088&utm_source=weekly&utm_medium=email&utm_campaign=202432

반응형

 

shimmer 3.0.0

컨탠츠의 로딩(Lazy update) 을 위화감 없이 표현하기 좋은 위젯

 

 

shimmer | Flutter package

A package provides an easy way to add shimmer effect in Flutter project

pub.dev

 

 

 

 

 

 

 

backdrop

popup 과 유사한 카드형태의 layout을 앞에 배치 할 수 있는 layout 구조를 제공합니다.

 

 

backdrop | Flutter package

Backdrop implementation in dart. (https://material.io/design/components/backdrop.html)

pub.dev

 

 

 

 

 

fl_chart

다양한 형태의 차트를 그릴수 있는 위젯

 

fl_chart | Flutter package

A highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart.

pub.dev

 

 

 

 

flutter_spinkit

다양한 로딩 인디케이터(loading indicator) 모음.

 

flutter_spinkit | Flutter package

A collection of loading indicators animated with flutter. Heavily inspired by @tobiasahlin's SpinKit.

pub.dev

 

 

 

 

...

 

 

 

BackDropFilter

- material에 포함되어있는 class 입니다.

이미지에 블러 처리 하기

 

 

다양한 위젯들을 활용해서 멋진 앱을 만들어 보아요~

 

flutter_app_badger

앱 아이콘에 배지를 표시해주는 패키지

 

flutter_app_badger | Flutter package

Plugin to update the app badge on the launcher (both for Android, iOS and macOS)

pub.dev

 

 

 

 

Carousel Slider 다양한 회전 슬라이더들

carousel 이라는 이름으로 찾아보면 다양한 것들이 나옵니다.




flutter_carousel_slider carousel_slider fan_carousel_image_slider
     

 

 

 

 

#해피코딩!!

반응형

오~~
Sun at Six 에서 복숭아아이스티 시켰는데.
복숭아를 갈아서 넣어주네요.




짱짱!!




'일상' 카테고리의 다른 글

스타필드: 노티드(knotted)  (0) 2024.03.08
영어공부  (0) 2023.09.25
가고싶은 여행지 링크  (0) 2014.07.17
근대 과학의 정모  (0) 2010.06.05
돌고있는 방향이 어느쪽인가요?  (0) 2010.06.04

+ Recent posts