fix
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
|||||||
*.sqlite
|
*.sqlite
|
||||||
*.lock
|
*.lock
|
||||||
*target
|
*target
|
||||||
|
*.db
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="referrer" content="origin-when-cross-origin">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.purple.min.css">
|
|
||||||
<title>atproto OAuth Web Service Example</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<hgroup>
|
|
||||||
{% if g.user %}
|
|
||||||
{% endif %}
|
|
||||||
</hgroup>
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
{% if g.user %}
|
|
||||||
<li><a href="{{ url_for('bsky_post') }}">Create Post</a>
|
|
||||||
<li><a href="{{ url_for('oauth_refresh') }}">Refresh Token</a>
|
|
||||||
<li><a href="{{ url_for('oauth_logout') }}">Logout</a>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="{{ url_for('oauth_login') }}">Login</a>
|
|
||||||
{% endif %}
|
|
||||||
<!--
|
|
||||||
<li><a href="https://github.com/bluesky-social/cookbook">Code</a>
|
|
||||||
-->
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<section class="content">
|
|
||||||
{% for message in get_flashed_messages() %}
|
|
||||||
<article>{{ message }}</article>
|
|
||||||
{% endfor %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,15 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% block content %}
|
|
||||||
{% if g.user %}
|
|
||||||
<div class="livestream">
|
|
||||||
<iframe id="livestream" title="stream" width="100%" height="700px" src="https://live.syui.ai/b9ec42d4-8a4d-4343-99fc-1bd1cdbc5a6f.html"> </iframe>
|
|
||||||
</div>
|
|
||||||
@<span class="user-handle">{{ g.user['handle'] }}</span>
|
|
||||||
<div class="livechat">
|
|
||||||
<iframe id="livechat" title="bskychat" width="100%" height="500" src="https://bbs.syui.ai/?handle={{ session['user_handle'] }}"> </iframe>
|
|
||||||
</div>
|
|
||||||
<style> /* .livechat { position: absolute; top: 0px; right:0px; } */ </style>
|
|
||||||
{% else %}
|
|
||||||
<p>Login to get started!</p>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -1,67 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Simple BBS</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ul>
|
|
||||||
{% for post in posts %}
|
|
||||||
<li><span class="user-post">{{ post }}</span></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<form action="/submit" method="post">
|
|
||||||
<input type="hidden" name="handle" id="handleInput">
|
|
||||||
<textarea name="content" required></textarea>
|
|
||||||
<input type="submit" value="Post">
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function getHandleFromUrl() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
return urlParams.get('handle');
|
|
||||||
}
|
|
||||||
window.onload = function() {
|
|
||||||
const handle = getHandleFromUrl();
|
|
||||||
if (handle) {
|
|
||||||
document.getElementById('handleInput').value = handle;
|
|
||||||
} else {
|
|
||||||
document.getElementById('handleInput').value = "anonymous";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
li {
|
|
||||||
width: 100%;
|
|
||||||
list-style: none;
|
|
||||||
padding: 10px 0px;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 5px;
|
|
||||||
resize: none;
|
|
||||||
border-bottom: 3px solid #2e7b96;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
input[type="submit"] {
|
|
||||||
border-radius: 5px;
|
|
||||||
width: 100%;
|
|
||||||
color: #fff;
|
|
||||||
background: #bb1a1a;
|
|
||||||
border: none;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 0px;
|
|
||||||
margin: 0 auto;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
span.user-post {
|
|
||||||
padding: 0px 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</html>
|
|
5
github/frontpage/packages-rs/drainpipe/.env.local
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
DATABASE_URL="drainpipe.db"
|
||||||
|
FRONTPAGE_CONSUMER_URL="http://${cloudflared}/api/receive_hook"
|
||||||
|
FRONTPAGE_CONSUMER_SECRET=`openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32`
|
||||||
|
|
||||||
|
#RELAY_URL=wss://syu.is
|
0
github/frontpage/packages-rs/drainpipe/.keep
Normal file
20
github/frontpage/packages-rs/drainpipe/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM rust:1.78-alpine AS builder
|
||||||
|
|
||||||
|
RUN apk add libressl-dev musl-dev sqlite-dev
|
||||||
|
|
||||||
|
WORKDIR /usr/src/unravel
|
||||||
|
COPY . .
|
||||||
|
# TODO: Use cargo-chef to cache dependencies compilation independently of the binary
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=/root/target \
|
||||||
|
cargo build --release --package drainpipe && \
|
||||||
|
# Move the release binary to a folder to be copied to the final image. It can't be copied directly from the target folder because it's in a cache mount
|
||||||
|
# See https://gist.github.com/noelbundick/6922d26667616e2ba5c3aff59f0824cd?permalink_comment_id=4379948#gistcomment-4379948
|
||||||
|
mv ./target/release /root
|
||||||
|
|
||||||
|
FROM alpine:3.14
|
||||||
|
COPY --from=builder /root/release/drainpipe /
|
||||||
|
|
||||||
|
ENV DATABASE_URL="/drainpipedata/drainpipe.db"
|
||||||
|
|
||||||
|
ENTRYPOINT ["/drainpipe"]
|
@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
drainpipe:
|
||||||
|
build:
|
||||||
|
dockerfile: ./packages-rs/drainpipe/Dockerfile
|
||||||
|
context: ../../
|
||||||
|
env_file:
|
||||||
|
- ./.env.local
|
||||||
|
volumes:
|
||||||
|
- ./drainpipedata:/drainpipedata
|
14
github/frontpage/packages/frontpage/.env.local
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
PRIVATE_JWK=`pnpm exec tsx ./scripts/generate-jwk.mts`
|
||||||
|
PUBLIC_JWK=`pnpm exec tsx ./scripts/generate-jwk.mts`
|
||||||
|
|
||||||
|
TURSO_CONNECTION_URL=libsql://xxx.turso.io
|
||||||
|
#TURSO_CONNECTION_URL=`turso db shell xxx-xxx`
|
||||||
|
TURSO_AUTH_TOKEN=`turso db tokens create xxx-xxx`
|
||||||
|
|
||||||
|
DRAINPIPE_CONSUMER_SECRET=`openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32`
|
||||||
|
VERCEL_PROJECT_PRODUCTION_URL=example.com
|
||||||
|
VERCEL_BRANCH_URL=example.com
|
||||||
|
|
||||||
|
#DRAINPIPE_CONSUMER_SECRET=secret
|
||||||
|
#TURSO_CONNECTION_URL=libsql://turso.dev.unravel.fyi
|
||||||
|
#PLC_DIRECTORY_URL=https://plc.dev.unravel.fyi
|
17
github/frontpage/packages/frontpage/Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
FROM node:20
|
||||||
|
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
WORKDIR /app
|
||||||
|
RUN git clone https://github.com/likeandscribe/frontpage
|
||||||
|
WORKDIR /app/frontpage
|
||||||
|
RUN pnpm i
|
||||||
|
RUN pnpm exec turbo run --affected type-check
|
||||||
|
|
||||||
|
WORKDIR /app/frontpage/packages/frontpage
|
||||||
|
COPY ./.env.local ./.env.local
|
||||||
|
COPY ./app ./app
|
||||||
|
COPY ./lib ./lib
|
||||||
|
RUN pnpm run db:generate
|
||||||
|
RUN pnpm run db:migrate
|
||||||
|
RUN pnpm run build
|
||||||
|
CMD [ "pnpm", "run", "start"]
|
8
github/frontpage/packages/frontpage/compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
frontpage:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- WATCHPACK_POLLING=true
|
226
github/frontpage/readme.md
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
# frontpage
|
||||||
|
|
||||||
|
- https://frontpage.fyi
|
||||||
|
- https://bsky.app/profile/frontpage.fyi
|
||||||
|
- https://github.com/likeandscribe/frontpage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ git clone https://github.com/likeandscribe/frontpage
|
||||||
|
$ dir=${0:a:h}/frontpage
|
||||||
|
$ cd $dir
|
||||||
|
```
|
||||||
|
|
||||||
|
## first setting
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd $dir
|
||||||
|
$ nvm use 20
|
||||||
|
$ pnpm i
|
||||||
|
$ cat turbo.json
|
||||||
|
$ pnpm exec turbo run --affected type-check
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd $dir/packages/frontpage
|
||||||
|
$ pnpm exec tsx ./scripts/generate-jwk.mts
|
||||||
|
# pnpm run db:generate
|
||||||
|
# pnpm run db:migrate
|
||||||
|
$ cat .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# frontpage/.env.local
|
||||||
|
PRIVATE_JWK=`pnpm exec tsx ./scripts/generate-jwk.mts`
|
||||||
|
PUBLIC_JWK=`pnpm exec tsx ./scripts/generate-jwk.mts`
|
||||||
|
|
||||||
|
TURSO_CONNECTION_URL=libsql://xxx.turso.io
|
||||||
|
#TURSO_CONNECTION_URL=`turso db shell xxx-xxx`
|
||||||
|
TURSO_AUTH_TOKEN=`turso db tokens create xxx-xxx`
|
||||||
|
|
||||||
|
DRAINPIPE_CONSUMER_SECRET=`openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32`
|
||||||
|
VERCEL_PROJECT_PRODUCTION_URL=example.com
|
||||||
|
VERCEL_BRANCH_URL=example.com
|
||||||
|
|
||||||
|
#DRAINPIPE_CONSUMER_SECRET=secret
|
||||||
|
#TURSO_CONNECTION_URL=libsql://turso.dev.unravel.fyi
|
||||||
|
#PLC_DIRECTORY_URL=https://plc.dev.unravel.fyi
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd $dir/packages-rs/drainpipe
|
||||||
|
$ cargo install diesel_cli --no-default-features --features sqlite
|
||||||
|
$ diesel setup
|
||||||
|
$ diesel migration run
|
||||||
|
$ ls drainpipe.db
|
||||||
|
$ cat .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# drainpipe/.env.local
|
||||||
|
DATABASE_URL="drainpipe.db"
|
||||||
|
FRONTPAGE_CONSUMER_URL="http://${cloudflared}/api/receive_hook"
|
||||||
|
FRONTPAGE_CONSUMER_SECRET=`openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32`
|
||||||
|
#RELAY_URL=wss://bsky.network
|
||||||
|
#FRONTPAGE_CONSUMER_SECRET=secret
|
||||||
|
```
|
||||||
|
|
||||||
|
## rewrite
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd $dir/packages/frontpage
|
||||||
|
$ PUBLIC_URL=example.com
|
||||||
|
$ grep -R frontpage.fyi ./app ./lib |cut -d : -f 1|sed -i "s/frontpage.fyi/$PUBLIC_URL/g"
|
||||||
|
|
||||||
|
$ HOST_REVERT=com.unravel.example
|
||||||
|
$ grep -R unravel.frontpage ./app ./lib |cut -d : -f 1|xargs sed -i "s/fyi.unravel.frontpage/${HOST_REVERT}/g"
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd $dir/packages-rs/drainpipe
|
||||||
|
$ HOST_REVERT=com.unravel.example
|
||||||
|
$ grep -R fyi.unravel.frontpage ./src |cut -d : -f 1|xargs sed -i "s/fyi.unravel.frontpage/${HOST_REVERT}/g"
|
||||||
|
```
|
||||||
|
|
||||||
|
## deploy
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd $dir/packages-rs/drainpipe
|
||||||
|
$ docker compose up
|
||||||
|
---
|
||||||
|
$ cd $dir/packages/frontpage
|
||||||
|
$ docker compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
## explanation
|
||||||
|
|
||||||
|
### client-metadata.json
|
||||||
|
|
||||||
|
the `client_id` is different between `pnpm run start` and `pnpm run dev`. see `https://localhost:3000/oauth/client-metadata.json` for this.
|
||||||
|
|
||||||
|
### local-infra
|
||||||
|
|
||||||
|
i think this is the server configuration required for self-hosting.
|
||||||
|
|
||||||
|
https://github.com/likeandscribe/frontpage/tree/main/packages/frontpage/local-infra
|
||||||
|
|
||||||
|
since plc gives an error, do the following. probably a postgres database is required. there is no need to open ports.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
plc:
|
||||||
|
image: ghcr.io/bluesky-social/did-method-plc:plc-f2ab7516bac5bc0f3f86842fa94e996bd1b3815b
|
||||||
|
container_name: plc
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '4000:8080'
|
||||||
|
depends_on:
|
||||||
|
- plc_db
|
||||||
|
env_file:
|
||||||
|
- ./plc.env
|
||||||
|
|
||||||
|
plc_db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: always
|
||||||
|
env_file:
|
||||||
|
- ./postgres.env
|
||||||
|
volumes:
|
||||||
|
- ./configs/postgres/init/:/docker-entrypoint-initdb.d/
|
||||||
|
- ./data/postgres/:/var/lib/postgresql/data/
|
||||||
|
healthcheck:
|
||||||
|
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
|
||||||
|
interval: 5s
|
||||||
|
retries: 20
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# plc.env
|
||||||
|
DEBUG_MODE=1
|
||||||
|
LOG_ENABLED=true
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
LOG_DESTINATION=1
|
||||||
|
PORT=8080
|
||||||
|
DATABASE_URL=postgres://postgres:postgres@plc_db/plc
|
||||||
|
DB_CREDS_JSON='{"username":"postgres","password":"postgres","host":"plc_db","port":"5432","database":"plc"}'
|
||||||
|
ENABLE_MIGRATIONS=true
|
||||||
|
DB_MIGRATE_CREDS_JSON='{"username":"postgres","password":"postgres","host":"plc_db","port":"5432","database":"plc"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# configs/postgres/init/init.sql
|
||||||
|
-- PLC
|
||||||
|
CREATE DATABASE plc;
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE plc TO postgres;
|
||||||
|
```
|
||||||
|
|
||||||
|
### pds
|
||||||
|
|
||||||
|
first, i think you need to get the pdsurl with oauth(session). if you have a session, you can perform operations such as posting.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd $dir/packages/frontpage
|
||||||
|
./lib/data/user.ts: const pdsUrl = await getPdsUrl(session.user.did);
|
||||||
|
```
|
||||||
|
|
||||||
|
it seems that drainpipe searches for `fyi.unravel.frontpage(collection)` in pds and commits it to firehose subscriberepos. if you change these two parts, it will not work with `frontpage.fyi`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// https://github.com/likeandscribe/frontpage/blob/e7444ec6c19f0ccef3776f04702c3bb033ed3bfc/packages-rs/drainpipe/src/main.rs#L66-L97
|
||||||
|
// RELAY_URL=wss://bsky.network
|
||||||
|
let mut ws_request = format!(
|
||||||
|
"{}/xrpc/com.atproto.sync.subscribeRepos{}",
|
||||||
|
relay_url, query_string
|
||||||
|
)
|
||||||
|
|
||||||
|
/// Process a message from the firehose. Returns the sequence number of the message or an error.
|
||||||
|
async fn process(message: Vec<u8>, ctx: &mut Context) -> Result<i64, ProcessError> {
|
||||||
|
let (_header, data) = firehose::read(&message).map_err(|e| ProcessError {
|
||||||
|
inner: e.into(),
|
||||||
|
seq: -1,
|
||||||
|
source: message.clone().into(),
|
||||||
|
kind: ProcessErrorKind::DecodeError,
|
||||||
|
})?;
|
||||||
|
let sequence = match data {
|
||||||
|
firehose::SubscribeRepos::Commit(commit) => {
|
||||||
|
let frontpage_ops = commit
|
||||||
|
.operations
|
||||||
|
.iter()
|
||||||
|
.filter(|op| op.path.starts_with("com.unravel.example."))
|
||||||
|
//.filter(|op| op.path.starts_with("fyi.unravel.frontpage."))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !frontpage_ops.is_empty() {
|
||||||
|
process_frontpage_ops(&frontpage_ops, &commit, &ctx)
|
||||||
|
.map_err(|e| ProcessError {
|
||||||
|
seq: commit.sequence,
|
||||||
|
inner: e,
|
||||||
|
source: message.clone().into(),
|
||||||
|
kind: ProcessErrorKind::ProcessError,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
commit.sequence
|
||||||
|
}
|
||||||
|
msg => msg.sequence(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(sequence)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## other
|
||||||
|
|
||||||
|
### license view
|
||||||
|
|
||||||
|
```html
|
||||||
|
// https://github.com/likeandscribe/frontpage/blob/de31aedf73c4e80e7376cf73c7c054437563f2ab/packages/frontpage/app/layout.tsx#L28-L52
|
||||||
|
+ <div dangerouslySetInnerHTML={{__html: '<!-- frontpage | MIT | https://github.com/likeandscribe/frontpage/blob/main/LICENSE -->'}} />
|
||||||
|
```
|
||||||
|
|
||||||
|
### admin view
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# https://github.com/likeandscribe/frontpage/blob/7fccf20fa800ba25fd57db279033ddf2cc92e9ce/packages/frontpage/lib/constants.ts
|
||||||
|
./lib/constants.ts:export const FRONTPAGE_ATPROTO_HANDLE = "admin.example.com";
|
||||||
|
|
||||||
|
# https://github.com/likeandscribe/frontpage/blob/cf8a4cb8bc7bab54407972964f8d39bf5e7c9182/packages/frontpage/app/(app)/layout.tsx#L55-L66
|
||||||
|
./app/\(app\)/layout.tsx:@admin.example.com <OpenInNewWindowIcon className="inline" />
|
||||||
|
```
|
||||||
|
|
@ -4,4 +4,4 @@ WORKDIR /app
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN pacman -Syu rye --noconfirm
|
RUN pacman -Syu rye --noconfirm
|
||||||
RUN rye sync
|
RUN rye sync
|
||||||
#CMD ["rye", "run", "flask", "run"]
|
|
3
github/python-oauth-web-app/app_add.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@app.route("/about")
|
||||||
|
def aboutpage():
|
||||||
|
return render_template("about.html")
|
@ -7,4 +7,4 @@ services:
|
|||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./demo.sqlite:/app/demo.sqlite
|
- ./demo.sqlite:/app/demo.sqlite
|
||||||
command: rye run flask run
|
command: rye run flask run --host=0.0.0.0
|
20
github/python-oauth-web-app/templates/about.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>service</h2>
|
||||||
|
<p>This service allows you to comment on live broadcasts using your atproto account.</p>
|
||||||
|
|
||||||
|
<h2>system</h2>
|
||||||
|
<p>Display posts to BBS using atproto oauth.</p>
|
||||||
|
<p>This service is generated using <a href="https://github.com/bluesky-social/cookbook">bluesky/cookbook</a>.</p>
|
||||||
|
<p>Authentication information will be deleted periodically.</p>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h2>サービス</h2>
|
||||||
|
<p>このサービスはlive配信にatprotoアカウントを使ってコメントができます。</p>
|
||||||
|
<h2>システム</h2>
|
||||||
|
<p>atproto oauthを使用してbbsへの書き込みを許可します。</p>
|
||||||
|
<p>このサービスは<a href="https://github.com/bluesky-social/cookbook">bluesky/cookbook</a>を使用して生成されています。</p>
|
||||||
|
<p>認証情報は定期的に削除されます。</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
114
github/python-oauth-web-app/templates/base.html
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="referrer" content="origin-when-cross-origin">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.blue.min.css">
|
||||||
|
<link rel="stylesheet" href="https://syui.ai/bower_components/font-awesome/css/all.min.css" />
|
||||||
|
<link rel="icon" href="https://live.syui.ai/favicon.ico">
|
||||||
|
<title>o.syui.ai</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<hgroup>
|
||||||
|
{% if g.user %}
|
||||||
|
{% endif %}
|
||||||
|
</hgroup>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/"><button class="secondary">o</button></a></li>
|
||||||
|
<li>.syui.ai</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
{% if g.user %}
|
||||||
|
<li><a href="{{ url_for('bsky_post') }}">post</a>
|
||||||
|
<li><a href="{{ url_for('oauth_refresh') }}">refresh</a>
|
||||||
|
<li><a href="{{ url_for('oauth_logout') }}"><button class="secondary">logout</button></a>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="/about"><i class="fa-solid fa-circle-question"></i></a></li>
|
||||||
|
{% endif %}
|
||||||
|
<!--
|
||||||
|
<li><a href="https://github.com/bluesky-social/cookbook">Code</a>
|
||||||
|
-->
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<section class="content">
|
||||||
|
{% for message in get_flashed_messages() %}
|
||||||
|
<article>{{ message }}</article>
|
||||||
|
{% endfor %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
button a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
iframe#livechat {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
iframe#livestream {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
button.oauth-login {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-1 {
|
||||||
|
display: flex;
|
||||||
|
.right-container {
|
||||||
|
background-color: #fff;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding:10px;
|
||||||
|
margin-left:30px;
|
||||||
|
.content:first-of-type {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-container {
|
||||||
|
padding:10px;
|
||||||
|
background-color: #fff;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 959px) {
|
||||||
|
.left-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
iframe#livestream {
|
||||||
|
width: 100%;
|
||||||
|
min-height:300px;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
.container-1 {
|
||||||
|
display: block;
|
||||||
|
.right-container {
|
||||||
|
margin-left:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<footer>©syui</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
8
github/python-oauth-web-app/templates/bsky_post.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form method="post">
|
||||||
|
<textarea name="post_text" placeholder="What's up?" id="post_text" required></textarea>
|
||||||
|
<input type="submit" value="Poast!">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
13
github/python-oauth-web-app/templates/error.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Error{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>⚠️ Error {{ status_code }} ⚠️</h2>
|
||||||
|
{% if err.description %}
|
||||||
|
<p><code>{{ err.description }}</code></p>
|
||||||
|
{% else %}
|
||||||
|
<p>Something went wrong!</p>
|
||||||
|
{% endif %}
|
||||||
|
<p><a href="/">Start Over</a></p>
|
||||||
|
{% endblock %}
|
BIN
github/python-oauth-web-app/templates/favicon.png
Normal file
After Width: | Height: | Size: 14 KiB |
1
github/python-oauth-web-app/templates/favicon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" ?><svg height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><title/><path d="M413.48,284.46c58.87,47.24,91.61,89,80.31,108.55-17.85,30.85-138.78-5.48-270.1-81.15S.37,149.84,18.21,119c11.16-19.28,62.58-12.32,131.64,14.09" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:32px"/><circle cx="256" cy="256" r="160" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:32px"/></svg>
|
After Width: | Height: | Size: 450 B |
29
github/python-oauth-web-app/templates/home.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container-1">
|
||||||
|
<div class="left-container">
|
||||||
|
<div class="livestream">
|
||||||
|
<iframe id="livestream" title="stream" src="https://live.syui.ai/b9ec42d4-8a4d-4343-99fc-1bd1cdbc5a6f.html"> </iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-container">
|
||||||
|
{% if g.user %}
|
||||||
|
<span class="user-handle">@{{ g.user['handle'] }}</span>
|
||||||
|
<div class="livechat">
|
||||||
|
<iframe id="livechat" title="bskychat" src="https://bbs.syui.ai/?handle={{ session['user_handle'] }}"> </iframe>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="livechat">
|
||||||
|
<a href="{{ url_for('oauth_login') }}"><button class="oauth-login">@</button></a>
|
||||||
|
<iframe id="livechat" title="bskychat" src="https://bbs.syui.ai"> </iframe>
|
||||||
|
</div>
|
||||||
|
<div class="right-container">
|
||||||
|
<p>write comment using oauth atproto.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
15
github/python-oauth-web-app/templates/login.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article>
|
||||||
|
<h3>Login with atproto</h3>
|
||||||
|
<form method="post">
|
||||||
|
<p>Provide your handle or DID to authorize an existing account with PDS.
|
||||||
|
<br>You can also supply a PDS/entryway URL (eg, <code>https://pds.example.com</code>).</p>
|
||||||
|
<fieldset role="group">
|
||||||
|
<input name="username" id="username" placeholder="handle.example.com" style="font-family: monospace,monospace;" required>
|
||||||
|
<input type="submit" value="Login">
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
@ -1,7 +1,8 @@
|
|||||||
FROM syui/aios
|
FROM syui/aios
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY ./src ./src
|
||||||
|
COPY ./templates ./templates
|
||||||
|
COPY ./Cargo.toml ./Cargo.toml
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
COPY ./templates /templates
|
|
||||||
CMD ["/app/target/release/rust-bbs"]
|
CMD ["/app/target/release/rust-bbs"]
|
@ -2,6 +2,6 @@ services:
|
|||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
- "8782:8782"
|
- 8080:8080
|
||||||
volumes:
|
volumes:
|
||||||
- ./sqlite.db:/app/sqlite.db
|
- ./sqlite.db:/app/sqlite.db
|
@ -79,13 +79,13 @@ async fn submit_post(
|
|||||||
.unwrap_or_else(|_| web::Query(QueryParams { handle: None }));
|
.unwrap_or_else(|_| web::Query(QueryParams { handle: None }));
|
||||||
//let handle = query.handle.clone().filter(|h| !h.is_empty());
|
//let handle = query.handle.clone().filter(|h| !h.is_empty());
|
||||||
//println!("Debug: Extracted handle: {:?}", handle);
|
//println!("Debug: Extracted handle: {:?}", handle);
|
||||||
let handle = if !form.handle.is_empty() {
|
let handle = if !form.handle.is_empty() {
|
||||||
form.handle.clone()
|
form.handle.clone()
|
||||||
} else {
|
} else {
|
||||||
query.handle.clone().unwrap_or_default()
|
query.handle.clone().unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("Debug: Using handle: {:?}", handle);
|
println!("Debug: Using handle: {:?}", handle);
|
||||||
|
|
||||||
let conn = Connection::open("sqlite.db")
|
let conn = Connection::open("sqlite.db")
|
||||||
.map_err(|_| ErrorInternalServerError("Database connection failed"))?;
|
.map_err(|_| ErrorInternalServerError("Database connection failed"))?;
|
||||||
@ -94,19 +94,19 @@ async fn submit_post(
|
|||||||
&[&form.handle, &form.content],
|
&[&form.handle, &form.content],
|
||||||
);
|
);
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let redirect_url = if !handle.is_empty() {
|
let redirect_url = if !handle.is_empty() {
|
||||||
format!("/?handle={}", handle)
|
format!("/?handle={}", handle)
|
||||||
} else {
|
} else {
|
||||||
"/".to_string()
|
"/".to_string()
|
||||||
};
|
};
|
||||||
Ok(HttpResponse::SeeOther()
|
Ok(HttpResponse::SeeOther()
|
||||||
.append_header(("Location",
|
.append_header(("Location",
|
||||||
redirect_url))
|
redirect_url))
|
||||||
.finish())
|
.finish())
|
||||||
},
|
},
|
||||||
|
|
||||||
//Ok(_) => Ok(web::Redirect::to("/").see_other()),
|
//Ok(_) => Ok(web::Redirect::to("/" + "?handle=" + handle).see_other()),
|
||||||
Err(_) => Err(ErrorInternalServerError("Failed to insert post")),
|
Err(_) => Err(ErrorInternalServerError("Failed to insert post")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.route("/post", web::get().to(post_form))
|
.route("/post", web::get().to(post_form))
|
||||||
.route("/submit", web::post().to(submit_post))
|
.route("/submit", web::post().to(submit_post))
|
||||||
})
|
})
|
||||||
.bind("0.0.0.0:8782")?
|
.bind("0.0.0.0:8080")?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
79
github/rust-bbs/templates/index.html
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Simple BBS</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="post-form" id="post-form">
|
||||||
|
<form action="/submit" method="post">
|
||||||
|
<input type="hidden" name="handle" id="handleInput">
|
||||||
|
<textarea name="content" required></textarea>
|
||||||
|
<input type="submit" value="Post">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="post-list">
|
||||||
|
<ul>
|
||||||
|
{% for post in posts %}
|
||||||
|
<li><span class="user-post">{{ post }}</span></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getHandleFromUrl() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return urlParams.get('handle');
|
||||||
|
}
|
||||||
|
window.onload = function() {
|
||||||
|
const handle = getHandleFromUrl();
|
||||||
|
if (handle) {
|
||||||
|
document.getElementById('handleInput').value = handle;
|
||||||
|
} else {
|
||||||
|
document.getElementById('handleInput').value = "anonymous";
|
||||||
|
var post = document.getElementById('post-form');
|
||||||
|
post.style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
overflow-y: scroll;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
width: 100%;
|
||||||
|
list-style: none;
|
||||||
|
padding: 10px 0px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
resize: none;
|
||||||
|
border-bottom: 3px solid #2060df;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
input[type="submit"] {
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 100%;
|
||||||
|
color: #fff;
|
||||||
|
background: #2060df;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
font-size 20px;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0 auto;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
span.user-post {
|
||||||
|
padding: 0px 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</html>
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 792 KiB After Width: | Height: | Size: 792 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
BIN
img/wiki-frontpage-0001.png
Normal file
After Width: | Height: | Size: 266 KiB |