Revit 2024のAddin開発
古くなってきていると情報を探すのが大変になるのでメモ。
Visual Studioプロジェクト
クラスライブラリ、.Net Framework 4.8あたりを選択。
.addinファイルの準備
<?xml version="1.0" encoding="utf-8"?> <RevitAddIns> <AddIn Type="Command"> <Name>MyPlugin</Name> <FullClassName>MyPlugin.Main</FullClassName> <Text>MyPlugin</Text> <Description>MyPlugin for sample</Description> <VisibilityMode>AlwaysVisible</VisibilityMode> <Assembly>.\MyPlugin.dll</Assembly> <AddInId>502fe383-2648-4e98-adf8-5e6047f9dc34</AddInId> <VendorId>xxxx</VendorId> <VendorDescription>xxx</VendorDescription> </AddIn> </RevitAddIns>
AddIn Typeは、"Command"以外にも"Application"とすることもできる。
Commandの場合は、FullClassNameで指定したクラスに、Autodesk.Revit.UI.IExternalCommandを実装する必要がある。 Applicationの場合は、FullClassNameで指定したクラスに、Autodesk.Revit.UI.IExternalApplicationを実装する必要がある。
参照の追加
- RevitAPI.dll
- RevitAPIUI.dll それぞれプロパティから「ローカルへコピー」を「いいえ」に変更しておく。
ビルド後の処理
xcopy /Y "$(TargetPath)" "$(AppData)\Autodesk\Revit\Addins\2024\" xcopy /Y "$(ProjectDir)MyPlugin.addin" "$(AppData)\Autodesk\Revit\Addins\2024\"
以下のフォルダに.addinをコピーすればRevit起動時に読み込まれる。
C:\ProgramData\Autodesk\Revit\Addins\2024C:\Users\ma38su\AppData\Roaming\Autodesk\Revit\Addins\2024
nextauthのAutodesk Platform Services対応プロバイダ
以前にも実装していたのだけれど、id_tokenの検証が有効になっていなかった。 あわせて対応しているpkce、state, nonceのチェックも追加した。
import { OAuthConfig } from "next-auth/providers/oauth" type AutodeskProfile = { sub: string, name: string, user_email: string, } export default function Autodesk( { clientId, clientSecret, logo, }: { clientId: string, clientSecret: string, logo: string, } ): OAuthConfig<AutodeskProfile> { return { id: "autodesk", name: "Autodesk", type: "oauth", version: '2.0', checks: ["pkce", "state", "nonce"], authorization: { url: "https://developer.api.autodesk.com/authentication/v2/authorize", params: { response_type: 'code', scope: "openid user:read data:create data:read data:write", }, }, token: 'https://developer.api.autodesk.com/authentication/v2/token', jwks_endpoint: 'https://developer.api.autodesk.com/authentication/v2/keys', userinfo: "https://api.userprofile.autodesk.com/userinfo", idToken: true, issuer: 'https://developer.api.autodesk.com', profile(profile) { const { sub: id, name, user_email: email } = profile; return { id, name, email } }, style: { logo, bg: "#000", text: "#fff", }, options: { clientId, clientSecret, }, } }
JSとデータベースでの時刻の取り扱い
JSのDate型について気になったので改めて確認した。
JSのDateはTimezoneを持っている。
const date = new Date()
このdateをコンソールで確認すると以下になる。
Wed May 01 2024 21:53:33 GMT+0900 (日本標準時)
Dateインスタンスをシリアライズする場合は、ISO 8601に従う。JSON.stringifyもそうなっている。
JSON.stringify({date1: date, date2: date.toISOString()})
は以下になる。
{ "date1":"2024-05-01T12:55:37.125Z", "date2":"2024-05-01T12:55:37.125Z" }
JSのDateインスタンスはタイムゾーン情報を保持するので、getTime()やtoISOStringで使う限りは、 クライアント、サーバサイドのタイムゾーンが異なる環境において、どこでnew Date()しても問題はない。
ISO 8601形式の時刻の文字列をDateインスタンスに戻すには、以下でよい。
new Date("2024-05-01T12:55:37.125Z")
インスタンスにする環境のタイムゾーンがJSTであれば以下に戻る。
Wed May 01 2024 21:53:33 GMT+0900 (日本標準時)
集計時
日毎のアクセス数を集計したいとする。 アクセスログから、アクセス数を日毎に集計する場合には、通常アクセスログのタイムスタンプを日時に丸め(切り落とし)て、集計する。 この時、データベースサーバにどのタイムゾーンでデータが保存されているかはよく確認しておかないと、集計される範囲がずれてしまうことがある。
たとえば、ORマッパーのprismaを使うとJSのDateインスタンスはUTCに直して保存される。 そのため、SQLで集計する場合は、タイムゾーンを適用する必要がある。
例えば、SQLサーバの場合の集計用のSQLを書いてみると以下になる。
select format(dateadd(hh, 9, time), 'yyyy-MM-dd') as date , email , count(*) as value from access group by format(dateadd(hh, 9, time), 'yyyy-MM-dd'), email order by date
このSQLでは、timeカラムの値をdateadd(hh, 9, time)でUTCから日本時間に変換し、その後format(dateadd(hh, 9, time), 'yyyy-MM-dd')で日時文字列に変換している。
これらの関数は方言があるのでデータベース毎に変更する必要はある。
書評:10年戦えるデータ分析入門: SQLを武器にデータ活用時代を生き抜く
気になって読んでみたらなかなかよかった。だいたい1日で大体読み切れたのもよい。
RDB/SQLの情報はだいたいOLTPを向いたものが多い中で、データ分析や活用を向いていて、内容も初歩的ではあるが実践的だと感じた。 SQL自体を解説している書籍では、網羅的だが、読むのが辛いことが多いのだが、本書は、SQL自体の使い方もデータ分析目的での説明なので、リファレンス的な本と比べて読みやすい。 さらに、SQLを使ったバッチの書き方や、SQL単体のテスト方法などもかかれていてなかなか興味深い。他の書籍ではあまりみたことがない、
とはいえ、書かれた時期がずいぶん前なので、Hadoopや商用製品周りの話は古くなっている。ゆえになつかしさがあって自分には逆によかった。
なお、著者の名前をみたことがある気がしたら、『ふつうのLinuxプログラミング』も読んでいたようだ。なかなかに良かった記憶。
NextAuth.jsで独自サインインページを作成する場合の注意すべきこと
NextAuth.jsには標準でもサインインページとサインアウトページは用意されているのだが、実務ではそのまま使いづらく、独自のサインインページを用意したいことが多いのではないかと思う。
本稿では、独自のサインインページを作成する場合の設定の概要と注意点を共有する。
AuthOptionsの設定
AuthOptionsのpagesにサインインページのパスを指定してやればよい。
import { AuthOptions } from "next-auth"; ... function newAuthOptions() { return { providers: [ AutodeskProvider({ ... }) ], ... pages: { signIn: '/signin', signOut: '/signin', error: '/signin', }, } satisfies AuthOptions; }
逆に言えば、このサインインページのパス/signInに独自のサインインページを作っておく必要がある。
サインインページの作成
独自のサインインページを作るにあたって、サインインサインアウトの処理はnext-auth/reactのsignIn, signOut関数をimportして、ボタンのonClickなどに割り当ててやればよい。
import { signIn, signOut } from "next-auth/react"; ... function SigninComponents(props: Props) { const params = useSearchParams(); const error = params.get('error'); const callbackUrl = params.get('callbackUrl') || "/"; async function signInHandle() { await signIn('autodesk', { callbackUrl }) }
signIn関数の呼び出しにあたっては、上記の例に示す通り、いくつかの引数を指定したほうが望ましい。
1番目の引数には、利用するOIDCプロバイダを指定する。OIDCプロバイダがひとつだけの場合は引数で指定することで、無駄なプロバイダ選択画面をスキップすることができる。
2番目の引数には、サインイン後にリダイレクトするURLをcallbackUrlフィールドに持たせて指定する必要がある。例えば、/sampleが認証が必要なページだとして、認証なしでアクセスすると、サインインページに飛ばされて認証を求められる。その際、認証後は当初アクセスしたかった/sampleにリダイレクトされることが親切である。 また、このcallbackUrlを指定しない場合は、サインインURLにそのまま戻ってきてしまう。今回は/signinというURLに専用のサインインページを設けるOptionを例に示したが、この例の場合は認証後は認証後にアクセスできるページにリダイレクトされてほしいのではないかと思う。その場合、指定されていない場合にリダイレクトするページを指定してやる必要がある。
このあたりのふるまいは意外とユーザの使い勝手に大きく影響するため、気を付けて設定しておきたい。設定については以上である。
NextAuth.jsのセッションからAccessTokenを取得できるようにする。
一般的な話であるが、OAuth認証の基本的な仕組みを前提として確認する。OAuth認証を利用するWebアプリは、OIDCプロバイダから提供されたアクセストークンの正しさを、ユーザプロファイル情報の取得可否によって確認する。つまり、認証の過程においてWebアプリは、OIDCプロバイダのアクセストークンを取得している。この取得したアクセストークンを保持しておいて、OIDCプロバイダの提供するAPI呼び出しに使おう考えるのはごく自然なことである。
そういうわけで、本記事ではNextAuth.jsでの認証後、アクセストークンを取得する方法について扱う。 NextAuth.jsの仕組み上、アクセストークンはセッションに保存し、利用する際もセッションデータから取得することになる。
本記事で扱ったNextAuth.jsのバージョンは4.24.5である。過去記事でも検討したAutodeskのAPSをOIDCプロバイダとして利用して検証した。
型情報の拡張
NextAuth.jsのSession型には、アクセストークンは含まれていない。そのため、型定義を拡張する必要がある。 とりあえず、以下のファイルを作成することで必要な型の拡張ができる。 (ここの細かい仕組みはよくわかっていない。)
/app/types/next-auth.d.ts
import { DefaultSession } from "next-auth"; import { JWT } from "next-auth/jwt"; declare module "next-auth" { interface Session { accessToken?: string; } } declare module "next-auth/jwt" { interface JWT { accessToken?: string; } }
認証処理の設定
callbacksに、sessionとjwtの処理を追加する。
/app/api/auth/[...nextauth]/option.ts
import { AuthOptions } from "next-auth"; ... function newAuthOptions() { return { providers: [ AutodeskProvider({ ... }) ], callbacks: { async session(params) { const { session, token } = params; // JWTトークンのaccessTokenをセッションへコピーする。 session.accessToken = token.accessToken; return session; }, async jwt(params) { const { token, account } = params; if (account) { // サインイン時にアクセストークンをJWTに保存する。 token.accessToken = account.access_token } return token }, }, ... } satisfies AuthOptions } export const options = newAuthOptions();
これで認証処理の設定はOK。アクセストークンはサインイン時にJWTに保存するので、テストする場合はサインインしなおす必要がある。
セッション情報の取得
サーバサイドでsessionを取得する場合は以下。
import { options } from "../auth/[...nextauth]/option"; export async function GET(req: Request) { const session = await getServerSession(options); ... }
getServerSessionの引数には認証処理を設定したoptionsを渡さないとアクセストークンがセッションに含まれてこない。
クライアントサイドの場合はSessionProviderでWrapしたうえでuseSessionで取得する。 App Routerを利用している場合はuse clientが必須。
"use client" import { SessionProvider } from "next-auth/react" import { useSession } from "next-auth/react" function App() { return <SessionProvider> <SessionView /> </SessionProvider> } function SessionView() { const { data: session } = useSession() return <pre>{JSON.stringify(session)}</pre> }
参考文献
公式に大体書いてある。
Next.JS App RouterにおいてSVGをAPIから出力する方法
SVGを最近よく活用している。CanvasよりReactとの相性はよいし、ベクターなのも好み。 (Three.jsを使えると面白いけど、、その機会はなかなかない。)
だいたいの場合は、HTMLに埋め込んで使えばいいので、特別なことをする必要はない。 ただ、たまに画像として保存したり、書き出したりしたいことがある。PPT用素材として使うとか、コンテンツとして独立させて、Markdownドキュメントから読み込ませるなど。
そういったケースにも対応するため、Next.JSのApp Router環境において、SVGをサーバサイドで生成して画像として提供する方法を探していたのだが、やっと解決策が見つかった。
以下のような感じ。
import React from "react"; function MySvg() { return ( <svg version="1.1" baseProfile="full" width="300" height="200" xmlns="http://www.w3.org/2000/svg"> <rect width="100%" height="100%" fill="red" /> <circle cx="150" cy="100" r="80" fill="green" /> <text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text> </svg> ) } export async function GET() { const ReactDOMServer = (await import('react-dom/server')).default const body = ReactDOMServer.renderToStaticMarkup(<MySvg />); return new Response( body, { status: 200, headers: { "Content-Type": "image/svg+xml", }, }); }
これで/api/svgへアクセスすると、MySVGの内容がブラウザに表示される。
ChartGPT 4に相談すると
import { renderToStaticMarkup } from 'react-dom/server'
を教えてくれた。助かる。
ただ、そのままimportすると、
Error: × You're importing a component that imports react-dom/server. To fix it, render or return the content directly as a Server Component instead for perf and security.
セキュリティの問題なのか、エラーがでてしまった。 githubながめてたらdynamic importで対応できるようだった。
const ReactDOMServer = (await import('react-dom/server')).default
staticなsvg生成の範囲では問題ないと思うのだが、使用する場合はリスクもしっかり検討する必要はありそうではある。
関連する試みとしては、過去にPNGをAPI経由で提供するケースも検討した。 ma38su.hatenablog.com