Blog

  • Tip for preventing crash issues when you migrate CoreData

    CoreData supports 3 type of migration

    • LightWeight Migrations
    • Staged Migrations
    • Manual Migrations

    Case 1. Manual Migration

    When you faced this issue, You need to check next version of the xcdatamodel.

    Case 2. LightWeight Migration

    Most common crash issues are caused by mismatching options in a properties. For example you have a Binary Data type field with external storage. And when your next xcdatamodel doesn’t matching with external storage options, it causes crash issues

    Lastly I recommend adding Arguments to investigate issues

    • -com.apple.CoreData.SQLDebug
    • -com.apple.CoreData.ConcurrencyDebug
    • -com.apple.CoreData.MigrationDebug

  • Swift Vapor: How to run a scheduled job

    Step 1. Install Redis on your Mac

    Follow the official guide

    https://redis.io/docs/latest/operate/oss_and_stack/install/archive/install-redis/install-redis-on-mac-os/

    brew install redis
    
    brew services start redis
    
    brew services info redis
    
    redis-cli
    

    Step 2. Setup Redis Configuration in Vapor App

    Add Redis Swift Package

    .package(url: "https://github.com/vapor/queues-redis-driver.git", from: "1.0.0")

    // swift-tools-version:6.0
    import PackageDescription
    
    let package = Package(
        name: "TestServer",
        platforms: [
            .macOS(.v13)
        ],
        dependencies: [
            // 💧 A server-side Swift web framework.
            .package(url: "https://github.com/vapor/vapor.git", from: "4.111.0"),
            .package(url: "https://github.com/vapor/fluent", from: "4.12.0"),
            .package(url: "https://github.com/vapor/fluent-sqlite-driver", from: "4.8.0"),
            .package(url: "https://github.com/vapor/sql-kit", from: "3.33.2"),
            .package(url: "https://github.com/lukaskubanek/LoremSwiftum", from: "2.2.3"),
            .package(url: "https://github.com/vapor/fluent-postgres-driver", from:"2.10.0"),
            .package(url: "https://github.com/vapor/jwt", from: "5.1.2"),
            .package(url: "https://github.com/vapor/queues-redis-driver.git", from: "1.0.0"),
        ],
        targets: [
            .target(
                name: "App",
                dependencies: [
                    .product(name: "Vapor", package: "vapor"),
                    .product(name: "Fluent", package: "fluent"),
                    .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
                    .product(name: "SQLKit", package: "sql-kit"),
                    .product(name: "LoremSwiftum", package: "LoremSwiftum"),
                    .product(name: "JWT", package: "jwt"),
                    .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
                    .product(name: "QueuesRedisDriver", package: "queues-redis-driver")
                ],
                swiftSettings: [
                    // Enable better optimizations when building in Release configuration. Despite the use of
                    // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release
                    // builds. See <https://github.com/swift-server/guides#building-for-production> for details.
                    .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
                ]
            ),
            .executableTarget(name: "Run", dependencies: [
                    .target(name: "App")
                ]
            ),
            .testTarget(name: "AppTests", dependencies: [
                .target(name: "App"),
                .product(name: "XCTVapor", package: "vapor")
            ])
        ]
    )
    
    


    Create an AsyncScheduledJob

    import Foundation
    import Vapor
    import Queues
    
    struct ScheduledJobs: AsyncScheduledJob {
        // Add extra services here via dependency injection, if you need them.
        func run(context: QueueContext) async throws {
            context.logger.info("Starting ScheduledJobs")
            print("✅ It is called")
            //Call other services using context.application.client
    //        context.application.client
            context.logger.info("ScheduledJobs completed")
        }
    }
    
    

    Register Scheduled Job in Vapor App

    https://docs.vapor.codes/advanced/queues/#available-builder-methods

    import Vapor
    import QueuesRedisDriver
    
    public func configure(_ app: Application) throws {
        let redisConfig = try RedisConfiguration(
            hostname: "127.0.0.1",
            port: 6379,
            pool: .init(
                maximumConnectionCount: .maximumActiveConnections(50),
                minimumConnectionCount: 10
            )
        )
        app.redis.configuration = redisConfig
        //Use Redis
        app.queues.use(.redis(redisConfig))
        
        //Register ScheduledJob - It runs every 30 seconds
        app.queues.schedule(ScheduledJobs())
            .minutely()
            .at(30)
        
        try app.queues.startScheduledJobs()
    
    ....
    }
    

    Conclusion

    Hope my articles helps you who want to run scheduled job using Redis. Please like my post or leave a comment it helps me continue share my knowledge for free.

  • 태국 치앙마이 체류 연장신청 후기 90 -> 97일

    태국 치앙마이 체류 연장신청 후기 90 -> 97일

    한국 여권은 태국에 90일까지 머물수 있다.

    작년에 치앙마이에서 정확하게 97일 연속으로 체류했었다.

    참고로 가장 쉬운 방법은 태국을 떠나 다른 나라로 갔다가 다시 태국으로 돌아오는 방법이다. 하지만 나는 그럴 여유가 없었고 영어학원 종료일과 콘도 계약기간이 7일이나 남아있어서 태국에 체류하면서 연장을 해야 했다.

    이 포스트는 태국에 머무르면서 1주일 연장 하는 방법에 대한 포스팅이다. (이 포스팅은 2024년 10월 기준 정보이다)

    태국은 반드시 90일 이상 체류 시 신고를 해야 된다.

    태국 체류 1주일 연장 신청 방법

    나는 디콘도에 살았었다. 가장 가까운 센트럴 치앙마이 몰에 이미그레이션 센터가 있다.

    쇼핑몰 안에 있는 이미그레이션 센터.

    방문 전에 알아야 할 것들과 준비해야 될 서류들

    먼저 TM30이라는 서류 양식이 있다. 정확하게 어떤 내용을 기입했는지 기억나지 않지만 집 계약문서 혹은 집주인으로부터 발급받아야 되는 걸로 기억한다.

    위의 서류는 거주자 신고서 양식이다. 이건 온라인으로 제출했었다.

    서류를 미리 작성하고 방문을 하면 이미그레이션 센터 직원에서 서류 검토를 해준다. 검토 후 이상이 없으면 다음과 같은 번호표를 발급해준다.

    비자체류 승인 완료

    비자 승인이 완료되면 여권에 위와 같은 도장을 찍어준다.

    원래 입국 시 찍어준 도장은 10월 22일까지 체류할 수 있었는데 체류 연장 이후 10월 29일까지 머무를 수 있도록 도장 찍어줬다.

  • 싱가포르 조호바루 기차표 예약

    싱가포르 조호바루 기차표 예약

    EasyBook.com에서 예약했다.

    https://www.easybook.com

    사이트에 가입해서 구매했다. 이전에는 싱가포리언 친구 차를 타고 편하게 갔었는데 이번에 처음으로 기차타고 가보기로 결심.

    싱가포르 우드랜드 기차역에서 JB Sentral까지는 금액이 편도로 인당 6.8불이다.

    반대로 JB Sentral에서 우드랜드로 돌아오는 표는 오히려 더 저렴하다. 인당 2.14불.

    결제는 PayNow로 완료함.

    티켓 영수증 확인하기

    생각보다 어렵지 않은 티케팅. 금액도 아주 저렴하고 만족스럽다. 다음 포스팅에서는 조호바루 여행 후기를 남길 예정.

  • Server-Side Swift – AWS Lambda with OpenAPI Generator Part 1

    This post is for whom want to use AWS Lambda with OpenAPI Generator. Official guide is useful but I felt there are some missing information. So I wrote this post. You can successfully run AWS Lambda function on your local machine and debug your code.

    Let’s start from very simple example. You only need 4 files. I’ll explain details. (Ignore Tests folder, no need it in this tutorial)

    • Package.swift
    • NativeMobileServer.swift
    • openapi.yaml
    • openapi-generator-config.yaml

    Step 1. Define OpenAPI Spec

    This OpenAPI spec is for tutorial.

    Please check folder and file structure. Create an openapi.yaml

    openapi: 3.1.0
    info:
      title: MobileJobService
      version: 1.0.0
    
    paths:
      /jobs/fetch:
        post:
          summary: Fetch job data from external source
          description: >
            This endpoint is called by a scheduled Lambda or backend service.
            It fetches job data from the given URL and processes it using the provided prompt.
          operationId: fetchJobs
          tags:
            - jobs
          requestBody:
            required: true
            content:
              application/json:
                schema:
                  $ref: '#/components/schemas/FetchJobsRequest'
          responses:
            '200':
              description: Successfully fetched and processed job data
              content:
                application/json:
                  schema:
                    $ref: '#/components/schemas/JobListResponse'
            '400':
              description: Invalid input parameters
            '500':
              description: Internal error during job fetch or processing
    
    components:
      schemas:
        FetchJobsRequest:
          type: object
          required:
            - url
            - prompt
          properties:
            url:
              type: string
              format: uri
              description: Target URL to scrape or fetch job data from
            prompt:
              type: string
              description: Instruction / extraction prompt used to parse the fetched page
    
        JobListResponse:
          type: array
          items:
            $ref: '#/components/schemas/Job'
    
        Job:
          type: object
          required:
            - id
            - title
          properties:
            id:
              type: string
              description: Unique identifier for the job
            title:
              type: string
              description: Job title
            country:
              type: string
              description: Country code (e.g. SG, US, TW)
            city:
              type: string
              description: City name (e.g. Singapore)
            postedAt:
              type: string
              format: date-time
              description: When this job was posted, if known
            company:
              type: string
              description: Company name
            team:
              type: string
              description: Team / department (e.g. Mobile, Backend, Growth)
            jobDescriptionLink:
              type: string
              format: uri
              description: Public link to full job description
            jobApplyLink:
              type: string
              format: uri
              description: Public link to apply
            salary:
              $ref: '#/components/schemas/Salary'
            description:
              type: string
              description: Cleaned / extracted full-text description for the role
    
        Salary:
          type: object
          properties:
            min:
              type: number
              description: Minimum compensation
            max:
              type: number
              description: Maximum compensation
            basis:
              type: string
              enum: [year, month]
              description: Salary period basis (yearly or monthly)
    
    

    And then create openapi-generator-config.yaml

    generate:
      - types
      - server
    
    

    Step 2. Create Swift Package Manger

    Package.swift

    // swift-tools-version: 6.2
    // The swift-tools-version declares the minimum version of Swift required to build this package.
    
    import PackageDescription
    
    let package = Package(
        name: "NativeMobileServer",
        platforms: [
            .macOS(.v15)
        ],
        products: [
            .executable(name: "NativeMobileServer", targets: ["NativeMobileServer"])
        ],
        dependencies: [
            .package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.10.3"),
            .package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.8.2"),
    
            .package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "2.0.0"),
            .package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.2.0"),
            .package(url: "https://github.com/awslabs/swift-openapi-lambda.git", from: "2.0.0"),
    
            .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),
        ],
        targets: [
            .executableTarget(
                name: "NativeMobileServer",
                dependencies: [
                    .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
    
                    .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
                    .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
    
                    .product(name: "OpenAPILambda", package: "swift-openapi-lambda"),
                    .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
                ],
                path: "Sources/NativeMobileServer",
                resources: [
                    .copy("openapi.yaml"),
                    .copy("openapi-generator-config.yaml")
                ],
                plugins: [
                    .plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")
                ]
            ),
            .testTarget(
                name: "NativeMobileServerTests",
                dependencies: ["NativeMobileServer"]
            ),
        ]
    )
    
    

    You can remove .testTarget if you don’t need to write test cases.


    Step 3. Write a main function

    //
    //  NativeMobileServer.swift
    //  NativeMobileServer
    //
    //  Created by Shawn Sungwook Baek on 10/26/25.
    //
    
    import Foundation
    import Logging
    import OpenAPILambda
    import OpenAPIRuntime
    
    @main
    struct JobServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
        func fetchJobs(_ input: Operations.fetchJobs.Input) async throws -> Operations.fetchJobs.Output
        {
            logger.info("fetchJobs invoked")
            let requestBody = input
           
            if case let .json(request) = requestBody.body {
                logger.info("Fetching jobs from \(request.url) with prompt: \(request.prompt)")
            }
    
            let mockJobs: [Components.Schemas.Job] = [
                .init(
                    id: "job-001",
                    title: "iOS Developer",
                    country: "SG",
                    postedAt: Date(),
                    company: "Apple",
                    team: "Mobile",
                    jobDescriptionLink: "https://example.com/job/1",
                    jobApplyLink: "https://example.com/apply/1",
                    salary: .init(min: 6000, max: 9000, basis: .month),
                    description: "Develop and maintain the iOS application."
                ),
                .init(
                    id: "job-002",
                    title: "Backend Engineer",
                    country: "TW",
                    postedAt: Date(),
                    company: "Uber",
                    team: "Server",
                    jobDescriptionLink: "https://example.com/job/2",
                    jobApplyLink: "https://example.com/apply/2",
                    salary: .init(min: 7000, max: 10000, basis: .month),
                    description: "Build API services and integrations."
                ),
            ]
            return .ok(.init(body: .json(mockJobs)))
        }
    
        let logger: Logger
    
        func register(transport: OpenAPILambdaTransport) throws {
            try transport.router.get("/health") { _, _ in
                "OK"
            }
            logger.trace("Available Routes\n\(transport.router)")
            // to log all requests and their responses, add a logging middleware
            let loggingMiddleware = LoggingMiddleware(logger: logger)
    
            // MANDATORY (middlewares are optional)
            try self.registerHandlers(on: transport, middlewares: [loggingMiddleware])
        }
    
        static func main() async throws {
            let openAPIService = JobServiceImpl(loggerLabel: "JobService")
            try await openAPIService.run()
    
        }
    
        init(loggerLabel: String) {
            var logger = Logger(label: loggerLabel)
            logger.logLevel = .trace
            self.logger = logger
        }
    }
    
    

    Step 4. Check Scheme in Xcode

    Check Executable Target.

    Important!

    You need to set Environment Variables

    • LOCAL_LAMBDA_SERVER_ENABLED
    • LOCAL_LAMBDA_PORT <- Optional! Default port is 7000

    Try build and run and check which pid is using 7000 port.

    lsof -i :7000
    

    If other program uses that port, you can change default port by setting LOCAL_LAMBDA_PORT

    When you run it, you will see the message. (In my case, I changed the default port 8000)

    Step 5. Invoke Lambda function

    This part you may confusing because you can’t call lambda like

    curl -v -X POST http://127.0.0.1:8000/jobs/fetch \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://example.com/jobs",
        "prompt": "Extract latest mobile job listings"
      }'
    

    If you want to see more detail information, check this out

    To invoke and debug our code, we should invoke function like this.

    If you want to learn more about this payload structure v2.0 in API Gateway check here

    curl -X "POST" "http://127.0.0.1:8000/invoke" \
         -H 'Content-Type: application/json; charset=utf-8' \
         -d $'{
      "requestContext": {
        "accountId": "",
        "time": "",
        "http": {
          "path": "/jobs/fetch",
          "userAgent": "",
          "method": "POST",
          "protocol": "HTTP/1.1",
          "sourceIp": "127.0.0.1"
        },
        "domainName": "",
        "timeEpoch": 0,
        "domainPrefix": "",
        "apiId": "",
        "requestId": "",
        "stage": "$default"
      },
      "rawPath": "/jobs/fetch",
      "rawQueryString": "",
      "version": "2.0",
      "routeKey": "$default",
      "isBase64Encoded": true,
      "body": "{\\"url\\":\\"https://jobs.apple.com/en-sg/search\\", \\"prompt\\":\\"Extract latest mobile job listings\\"}"
    }'
    


    I’ve set breakpoint at here.

    Let’s invoke function.

    Ok, breakpoint is working.

    And I can see the results from AWS Lambda function

    Conclusion

    Swift is very powerful for developing server-side applications. There a lot of great open source projects. In Part 2, I’ll explain how to deploy AWS Lambda Swift function to the AWS using SAM CLI.

  • Apply Swift Format

    Apply Swift Format

    Swift Format is made by Apple. If your Xcode Version is latest version (after Xcode 16), you don’t need to install it. Toolchain contains swift format.

    xcrun --find swift-format
    
    ///Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-format
    

    https://github.com/swiftlang/swift-format?tab=readme-ov-file#configuring-the-command-line-tool

    Step 1. Create swift-format.json

    swift-format dump-configuration > swift-format.json
    
    //It will show default settings for formatting and listing
    

    Copied default settings and create a file like swift-format.json in your project root directory.

    SampleProject/
    ├── 📱 SampleProject.xcworkspace
    ├── 🔧 SampleProject.xcodeproj
    │
    ├── swift-format.json               # Swift Format
    ├── 📦 Main App & Server
    │   ├── SampleProject/              # iOS 
    │   ├── SampleProjectTests/
    │   └── SampleProjectServer/        # Server
    │
    ├── 🧩 Modules (Core Libraries)
    │
    ├── 📦 Dependencies
    │   └── Packages/                  # SPM packages
    │
    └── 📄 Config Files
        ├── SampleProject.xcconfig
        └── GoogleService-Info.plist
    

    Step 2. Add Build Script

    echo "🐥 Formatting"
    swift-format format --configuration swift-format.json --ignore-unparsable-files -i -p -r ${PROJECT_DIR}
        
    echo "🐥 Linting"
    swift-format lint --configuration swift-format.json --ignore-unparsable-files -p -r ${PROJECT_DIR}
    

    Step 3. Build Project

    Okay It works

  • Setup C/C++ on the iPad using iSH Shell

    Setup C/C++ on the iPad using iSH Shell

    Do you want to use a terminal on your iPad?

    I recommend iSH Shell.

    You can install packages on your iPad and it works on the offline.

    Step 1. Download iSH shell

    https://apps.apple.com/kr/app/ish-shell/id1436902243?l=en-GB

    Step 2. Install APK packages

    apk update
    
    apk add gcc g++ make musl-dev
    
    apk add git
    
    apk add vim
    

    Step 3. Install zsh

    apk add zsh curl git
    
    sh -c “$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)”
    

    And set zsh as default

    ask add shadow
    
    passwd
    
    chsh -s /bin/zsh
    

    To run chsh, you have to set your password. But on the iSH shell, you haven’t set any password. So please set a new password by running passwd.

    And then run chsh -s /bin/zsh

    Step 4. Write C++ Code

    vim hello.cpp
    
    #include <iostream>;
    using namespace std;
    
    int main() {
        cout &lt;&lt; “Hellow from iPad” &lt;&lt; endl;
        return 0;
    }
    
    g++ hello.cpp -o hello
    
    ./hello
    

    Isn’t it cool? 😎 Happy c++ coding on your iPad on the offline!

  • How to Validate an OpenAPI Spec Using IBM OpenAPI Validator

    How to Validate an OpenAPI Spec Using IBM OpenAPI Validator

    This post is the second part of our OpenAPI series. Let’s set up GitHub Actions for validating an OpenAPI spec before merging into the main branch.

    Check more details

    Step 1. Install IBM OpenAPI Validator

    npm i -g ibm-openapi-validator
    npm i @ibm-cloud/openapi-ruleset
    

    Step 2. Setup OpenAPI Rullset

    extends: '@ibm-cloud/openapi-ruleset'
    rules:
      ibm-accept-and-return-models: info
      ibm-integer-attributes: false
      ibm-required-array-properties-in-response: false
      ibm-property-casing-convention: false
    
    

    Create an file – validator-rules.yaml

    You can check details

    ibm rules (inherent from spectral:as rules)

    spectral:oas rules

    Step 3. Setup Configurations

    Create an file – validator-config.yml

    errorsOnly: true
    colorizeOutput: true
    limits:
      warnings: 25
    outputFormat: 'text'
    summaryOnly: false
    files:
      - api.yml
    ignoreFiles:
      - validator-config.yml
    logLevels:
      root: error
      ibm-schema-description-exists: debug
    ruleset: ./validator-rules.yaml
    produceImpactScore: false
    markdownReport: true
    
    

    Check document

    Step 4. Check Project Folders and Files

    Step 5. Run Validator on your Local Machine

    lint-openapi -c ./validator-config.yml ./api.yml --errors-only --no-colors
    
    

    After run the command, It will generate reporting file (md)

    • api-validator-report.md

    Step 6. Add .gitignore file

    node_modules/
    package-lock.json
    package.json
    api-validator-report.md
    

    Step 7. Setup Github Action

    name: Validate OpenAPI Documentation
    on:
      pull_request:
        branches: [main]
      push:
        branches: [main]
    
    jobs:
      validate:
        name: Validate OpenAPI Documentation
        runs-on: ubuntu-latest
        permissions:
          contents: read
          pull-requests: write
    
        steps:
          - name: Checkout code
            uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '20'
    
          - name: Install IBM OpenAPI Validator
            run: |
              npm i -g ibm-openapi-validator
              npm i @ibm-cloud/openapi-ruleset
    
          - name: Validate api.yml
            id: validation
            run: |
              if lint-openapi -c ./validator-config.yml ./api.yml --errors-only --no-colors > validation_result.txt 2>&1; then
                echo "validation_status=success" >> $GITHUB_OUTPUT
              else
                echo "validation_status=failed" >> $GITHUB_OUTPUT
              fi
            continue-on-error: true
    
          - name: Comment PR with validation output
            if: github.event_name == 'pull_request'
            uses: actions/github-script@v7
            with:
              script: |
                const fs = require('fs');
                const validationStatus = '${{ steps.validation.outputs.validation_status }}';
                const validationOutput = fs.readFileSync('api-validator-report.md', 'utf8');
                  
                let body = '';
                
                if (validationStatus === 'success') {
                  body = `## ✅ OpenAPI Validation Passed
                  
                  **File**: \`./api.yml\`
                  **Commit**: \`${{ github.sha }}\`
                  
                  ${validationOutput}
                  
                  `;
                } else {
                  body = `## ❌ OpenAPI Validation Failed
                                
                  **File**: \`./api.yml\`
                  **Commit**: \`${{ github.sha }}\`
                  
                  ${validationOutput}
                  
                  `;
                }
                
                // Truncate if too long for GitHub comment limit
                if (body.length > 65000) {
                  body = body.slice(0, 65000) + '\n\n... (truncated due to GitHub comment length limit)';
                }
                
                // Find existing comment and update or create new one
                const { data: comments } = await github.rest.issues.listComments({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: context.issue.number,
                });
                
                const marker = 'OpenAPI Validation';
                const existing = comments.find(c => c.body && c.body.includes(marker));
                
                if (existing) {
                  await github.rest.issues.updateComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    comment_id: existing.id,
                    body
                  });
                } else {
                  await github.rest.issues.createComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    issue_number: context.issue.number,
                    body
                  });
                }
    
    
          - name: Fail job if validation failed
            if: steps.validation.outputs.validation_status == 'failed'
            run: exit 1
    
    

    Create Github Action yml file

    • .github/workflows/validator.yml

    ⚠️ Check md file name – api-validator-report.md

    const validationOutput = fs.readFileSync('api-validator-report.md', 'utf8');
    

    Step 8. Check Github Action’s results

    This is the last step, let’s check the results.

    When you create a PR that targets main branch, our GitHub action will run and comment results on your Opened PR.

    IBM OpenAPI Validator provides very detailed information. To fix issues, You can update your OpenAPI Spec file or You can change the rulesets if you don’t want to change your OpenAPI Spec files.

    BTW This validator is really helpful it prevent wrong OpenAPI spec merged into main branch.

    Related Posts

  • From OpenAPI Spec to Live Documentation: AWS Amplify Deploy Guide

    From OpenAPI Spec to Live Documentation: AWS Amplify Deploy Guide

    I choose the AWS Amplify for deploying OpenAPI Static Website.

    It is very simple.

    Step 1. Create a Repo

    I have only 2 files in my repo.

    • index.html
    • api.yml

    You can follow the official guide.

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta name="description" content="SwaggerUI" />
      <title>SwaggerUI</title>
      <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
    </head>
    <body>
    <div id="swagger-ui"></div>
    <script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js" crossorigin></script>
    <script>
      window.onload = () => {
        window.ui = SwaggerUIBundle({
          url: './api.yml',
          dom_id: '#swagger-ui',
        });
      };
    </script>
    </body>
    </html>
    

    This is my index.html file.

    Step 2. Create Amplify Projects

    Login AWS, Search Amplify and Create a new App.

    • Don’t select template (e.g, Next.js, Due, Angular and Vite. We don’t need it)

    And Select your GitHub Repository and Branch. (You don’t need to make your Repo as Public, Private Repo also is working fine)

    Protect your site if you want

    • Set an Username and Password

    Ok, that’s all. Click Save and deploy!

    Click Monitoring and check your Domain. Open this url.

    If you set Username and Password, You must Sign In to access your Website

    All done, Your OpenAPI Document Site is Live now!

    Step 3. Setup Domain

    If you want to set your custom domain for your website, follow this guidelines

    I recommend Route53. If your domain already registered in Route53, setup custom domain is very easy.

    Enter your subdomains. And Click Add domain button.

    It takes 2-3 minutes. Just wait a bit. You will access your static website using your subdomain.

    Conclusion

    Using Amplify is super simple to deploy the static website. You don’t need to extra setup. When your branch has updated, amplify will automatically detect that changes and deploy it.

    Next topic, I’ll share how to verify OpenAPI spec file using IBM OpenAPI Validator. https://github.com/IBM/openapi-validator

    Related Post

  • 치앙마이 90일 + 7일 살아보기 – 집 계약

    치앙마이 90일 + 7일 살아보기 – 집 계약

    레이오프로 인해 싱가포르 비자가 만료된 이후로 치앙마이에서 살아보기로 결정했다. 이때만 해도 발리와 치앙마이 사이에서 고민했었는데 한명이라도 아는 사람이 살고 있는 곳으로 가는 것이 좋을 것 같아서 치앙마이로 결정했다.

    암튼 치앙마이에서 살고있는 전 직장 동료가 정착 관련해서 정말 많이 도와주었다.

    일단 3개월만 살아보면서 다시 싱가포르로 돌아가는 것이 목표였어서 단기로 콘도 임대 가능한 사이트인 PERFECT HOMES에서 집을 구했다.

    위치 선정

    일단 가성비 좋은 아이콘 호텔 (1박에 4-5만원대)에서 1주일 머무르면서 콘도 뷰잉을 했다. 콘도 위치를 선정할 때 가장 최우선으로 고려했던 것은 다음과 같다.

    1. 대형 쇼핑몰이 걸어서 갈 거리에 있을 것
    2. 영어학원이 가까울 것

    위의 2가지 조건이 최우선 순위였다. 그렇게 해서 님만 근처 콘도도 뷰잉하고 센트럴 치앙마이 근처도 뷰잉한 끝에 D Condo Nim 1 베드룸으로 최종 결정했다.

    콘도 뷰잉

    콘도 선정 시 포기하고 싶지 않았던 것 중에 하나는 수영장과 헬스장이었다. 뷰잉은 1-2틀에 몰아서 보고 바로 결정하였다.

    D Vieng 콘도 (올드타운 근처)

    d condo nim (최종선택, 센트럴 페스티벌 쇼핑몰 근처)

    스프링 콘도

    The One 콘도

    사실 콘도는 더 원이 가장 좋았다. 부대시설도 좋고 코워킹 스페이스에 수영장도 가장 컸다. 하지만 1 베드룸이 없었고 2 베드룸도 봤는데 작은 방은 정말 침대 하나만 들어가고 여유 공간이 없었다. 암튼 여기까지 콘도 뷰잉을 하고 D Condo Nim으로 확정!

    콘도 계약 완료

    입주 날짜를 결정하면 3개월 계약 기준으로 2달치 보증금을 내야 된다. 그리고 인터넷도 따로 신청해야 된다.

    싱가포르 생활을 정리하고 가져온 짐들.. 거의 피난민 수준

    마치며

    1년이 지나서야 작년에 있었던 일들을 정리하면서 블로그를 쓰고 있다. 이제서야 여유가 생겼다고 해야 될까? 현재는 싱가포르로 돌아와서 다시 새로운 직장에서 잘 적응하고 살고 있다. 아무튼 작년에 있었던 치앙마이 생활 및 취업준비 내용을 계속 포스팅 할 예정이다.