From 1c0d6a309b742e068d599e7e53f17a2d6580a773 Mon Sep 17 00:00:00 2001 From: Andre Henriques Date: Fri, 23 Feb 2024 23:49:23 +0000 Subject: [PATCH] chore: started working on the api --- go.mod | 1 + go.sum | 2 + logic/utils/handler.go | 48 ++++++++ users.go | 36 +++++- webpage/.eslintignore | 13 +++ webpage/.eslintrc.cjs | 31 ++++++ webpage/.gitignore | 10 ++ webpage/.npmrc | 1 + webpage/.prettierignore | 4 + webpage/.prettierrc | 8 ++ webpage/README.md | 38 +++++++ webpage/bun.lockb | Bin 0 -> 99104 bytes webpage/package.json | 34 ++++++ webpage/src/NavBar.svelte | 60 ++++++++++ webpage/src/app.d.ts | 13 +++ webpage/src/app.html | 12 ++ webpage/src/lib/index.ts | 1 + webpage/src/lib/requests.svelte.ts | 42 +++++++ webpage/src/routes/+error.svelte | 17 +++ webpage/src/routes/+layout.svelte | 9 ++ webpage/src/routes/+layout.ts | 4 + webpage/src/routes/+page.svelte | 1 + webpage/src/routes/Page404.svelte | 54 +++++++++ webpage/src/routes/UserStore.svelte.ts | 55 +++++++++ webpage/src/routes/login/+page.svelte | 83 ++++++++++++++ webpage/src/routes/logout/+page.svelte | 10 ++ webpage/src/styles/app.css | 88 +++++++++++++++ webpage/src/styles/fonts.css | 8 ++ webpage/src/styles/forms.css | 147 +++++++++++++++++++++++++ webpage/static/favicon.png | Bin 0 -> 1571 bytes webpage/svelte.config.js | 22 ++++ webpage/tsconfig.json | 18 +++ webpage/vite.config.ts | 6 + 33 files changed, 873 insertions(+), 3 deletions(-) create mode 100644 webpage/.eslintignore create mode 100644 webpage/.eslintrc.cjs create mode 100644 webpage/.gitignore create mode 100644 webpage/.npmrc create mode 100644 webpage/.prettierignore create mode 100644 webpage/.prettierrc create mode 100644 webpage/README.md create mode 100755 webpage/bun.lockb create mode 100644 webpage/package.json create mode 100644 webpage/src/NavBar.svelte create mode 100644 webpage/src/app.d.ts create mode 100644 webpage/src/app.html create mode 100644 webpage/src/lib/index.ts create mode 100644 webpage/src/lib/requests.svelte.ts create mode 100644 webpage/src/routes/+error.svelte create mode 100644 webpage/src/routes/+layout.svelte create mode 100644 webpage/src/routes/+layout.ts create mode 100644 webpage/src/routes/+page.svelte create mode 100644 webpage/src/routes/Page404.svelte create mode 100644 webpage/src/routes/UserStore.svelte.ts create mode 100644 webpage/src/routes/login/+page.svelte create mode 100644 webpage/src/routes/logout/+page.svelte create mode 100644 webpage/src/styles/app.css create mode 100644 webpage/src/styles/fonts.css create mode 100644 webpage/src/styles/forms.css create mode 100644 webpage/static/favicon.png create mode 100644 webpage/svelte.config.js create mode 100644 webpage/tsconfig.json create mode 100644 webpage/vite.config.ts diff --git a/go.mod b/go.mod index eb658de..4b001f6 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect diff --git a/go.sum b/go.sum index 755022d..8032581 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99 h1:8Bt1P/zy1gb37L4n8C github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= diff --git a/logic/utils/handler.go b/logic/utils/handler.go index 6e37a1a..decdef4 100644 --- a/logic/utils/handler.go +++ b/logic/utils/handler.go @@ -14,6 +14,7 @@ import ( dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" "github.com/charmbracelet/log" + "github.com/goccy/go-json" ) func Mul(n1 int, n2 int) int { @@ -347,6 +348,43 @@ type Context struct { Db *sql.DB } +func (c Context) ToJSON(r *http.Request, dat any) *Error { + + decoder := json.NewDecoder(r.Body) + + err := decoder.Decode(dat) + if err != nil { + return c.Error500(err) + } + + return nil +} + +func (c Context) SendJSON(w http.ResponseWriter, dat any) *Error { + w.Header().Add("content-type", "application/json") + text, err := json.Marshal(dat) + if err != nil { + return c.Error500(err) + } + if _, err = w.Write(text); err != nil { + return c.Error500(err) + } + return nil +} + +func (c Context) SendJSONStatus(w http.ResponseWriter, status int, dat any) *Error { + w.Header().Add("content-type", "application/json") + w.WriteHeader(status) + text, err := json.Marshal(dat) + if err != nil { + return c.Error500(err) + } + if _, err = w.Write(text); err != nil { + return c.Error500(err) + } + return nil +} + func (c Context) Error400(err error, message string, w http.ResponseWriter, path string, base string, data AnyMap) *Error { c.SetReportCaller(true) c.Logger.Error(message) @@ -609,6 +647,10 @@ func NewHandler(db *sql.DB) *Handle { if r.Header.Get("Request-Type") == "htmlfull" { ans = HTMLFULL } + if r.Header.Get("content-type") == "application/json" { + ans = JSON + } + //TODO JSON //Login state @@ -618,6 +660,9 @@ func NewHandler(db *sql.DB) *Handle { return } + w.Header().Add("Access-Control-Allow-Origin", "*") + w.Header().Add("Access-Control-Allow-Headers", "*") + if r.Method == "GET" { x.handleGets(w, r, context) return @@ -630,6 +675,9 @@ func NewHandler(db *sql.DB) *Handle { x.handleDeletes(w, r, context) return } + if r.Method == "OPTIONS" { + return + } panic("TODO handle method: " + r.Method) }) diff --git a/users.go b/users.go index 94b38b0..9405b95 100644 --- a/users.go +++ b/users.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "database/sql" "encoding/hex" - "fmt" "io" "net/http" "time" @@ -79,8 +78,39 @@ func usersEndpints(db *sql.DB, handle *Handle) { handle.GetHTML("/login", AnswerTemplate("login.html", nil, 0)) handle.Post("/login", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if c.Mode == JSON { - fmt.Println("Handle JSON") - return &Error{Code: 404} + + type UserLogin struct { + Email string `json:email` + Password string `json:password` + } + + var dat UserLogin + + if err := c.ToJSON(r, &dat); err != nil { + return err + } + + + /*if (dat["email"] == nil || dat["password"] == nil) { + // TODO improve this + c.Logger.Warn("Email or password are empty") + return c.Error500(nil) + }*/ + + // TODO Give this to the generateToken function + expiration := time.Now().Add(24 * time.Hour) + token, login := generateToken(db, dat.Email, dat.Password) + if !login { + return c.SendJSONStatus(w, http.StatusUnauthorized, "Email or password are incorrect") + } + + + cookie := &http.Cookie{Name: "auth", Value: token, HttpOnly: false, Expires: expiration} + http.SetCookie(w, cookie) + + w.Header().Set("Location", "/") + w.WriteHeader(http.StatusSeeOther) + return nil } r.ParseForm() diff --git a/webpage/.eslintignore b/webpage/.eslintignore new file mode 100644 index 0000000..3897265 --- /dev/null +++ b/webpage/.eslintignore @@ -0,0 +1,13 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/webpage/.eslintrc.cjs b/webpage/.eslintrc.cjs new file mode 100644 index 0000000..0b75758 --- /dev/null +++ b/webpage/.eslintrc.cjs @@ -0,0 +1,31 @@ +/** @type { import("eslint").Linter.Config } */ +module.exports = { + root: true, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:svelte/recommended', + 'prettier' + ], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + parserOptions: { + sourceType: 'module', + ecmaVersion: 2020, + extraFileExtensions: ['.svelte'] + }, + env: { + browser: true, + es2017: true, + node: true + }, + overrides: [ + { + files: ['*.svelte'], + parser: 'svelte-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser' + } + } + ] +}; diff --git a/webpage/.gitignore b/webpage/.gitignore new file mode 100644 index 0000000..6635cf5 --- /dev/null +++ b/webpage/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/webpage/.npmrc b/webpage/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/webpage/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/webpage/.prettierignore b/webpage/.prettierignore new file mode 100644 index 0000000..cc41cea --- /dev/null +++ b/webpage/.prettierignore @@ -0,0 +1,4 @@ +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/webpage/.prettierrc b/webpage/.prettierrc new file mode 100644 index 0000000..9573023 --- /dev/null +++ b/webpage/.prettierrc @@ -0,0 +1,8 @@ +{ + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "plugins": ["prettier-plugin-svelte"], + "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] +} diff --git a/webpage/README.md b/webpage/README.md new file mode 100644 index 0000000..5ce6766 --- /dev/null +++ b/webpage/README.md @@ -0,0 +1,38 @@ +# create-svelte + +Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npm create svelte@latest + +# create a new project in my-app +npm create svelte@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. diff --git a/webpage/bun.lockb b/webpage/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..3db0581a0ef8efe79df21f1efd6cdc30693252f2 GIT binary patch literal 99104 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

d{gK7op=J$r<5 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 + import { userStore } from './routes/UserStore.svelte'; + + +

+ + diff --git a/webpage/src/app.d.ts b/webpage/src/app.d.ts new file mode 100644 index 0000000..743f07b --- /dev/null +++ b/webpage/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/webpage/src/app.html b/webpage/src/app.html new file mode 100644 index 0000000..77a5ff5 --- /dev/null +++ b/webpage/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/webpage/src/lib/index.ts b/webpage/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/webpage/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/webpage/src/lib/requests.svelte.ts b/webpage/src/lib/requests.svelte.ts new file mode 100644 index 0000000..13738d4 --- /dev/null +++ b/webpage/src/lib/requests.svelte.ts @@ -0,0 +1,42 @@ +import { userStore } from 'routes/UserStore.svelte'; + +const API = "http://localhost:8000"; + +export async function get(url: string) { + const headers = new Headers(); + //headers.append('content-type', 'application/json'); + if (userStore.user) { + headers.append('token', userStore.user.token); + } + + let r = await fetch(`${API}/${url}`, { + method: 'GET', + headers: headers, + }); + + if (r.status !== 200) { + throw r; + } + + return r.json(); +} + +export async function post(url: string, body: any) { + const headers = new Headers(); + headers.append('content-type', 'application/json'); + if (userStore.user) { + headers.append('token', userStore.user.token); + } + + let r = await fetch(`${API}/${url}`, { + method: 'POST', + headers: headers, + body: JSON.stringify(body), + }); + + if (r.status !== 200) { + throw r; + } + + return r.json(); +} diff --git a/webpage/src/routes/+error.svelte b/webpage/src/routes/+error.svelte new file mode 100644 index 0000000..b08aa0e --- /dev/null +++ b/webpage/src/routes/+error.svelte @@ -0,0 +1,17 @@ + + +{#if $page.error} + {#if $page.status == 404} + + {:else} +

+ {$page.status} +

+

+ {$page.error.message} +

+ {/if} +{/if} diff --git a/webpage/src/routes/+layout.svelte b/webpage/src/routes/+layout.svelte new file mode 100644 index 0000000..9d2737a --- /dev/null +++ b/webpage/src/routes/+layout.svelte @@ -0,0 +1,9 @@ + + + + + diff --git a/webpage/src/routes/+layout.ts b/webpage/src/routes/+layout.ts new file mode 100644 index 0000000..2dedff1 --- /dev/null +++ b/webpage/src/routes/+layout.ts @@ -0,0 +1,4 @@ +export const prerender = true; +export const ssr = false; +export const csr = true; +//export const trailingSlash = ''; diff --git a/webpage/src/routes/+page.svelte b/webpage/src/routes/+page.svelte new file mode 100644 index 0000000..9845c90 --- /dev/null +++ b/webpage/src/routes/+page.svelte @@ -0,0 +1 @@ +

Main Web Page

diff --git a/webpage/src/routes/Page404.svelte b/webpage/src/routes/Page404.svelte new file mode 100644 index 0000000..e45f516 --- /dev/null +++ b/webpage/src/routes/Page404.svelte @@ -0,0 +1,54 @@ + + +
+
+

+ 404 +

+ {#if message} +

+ {message} +

+ {#if goBackLink} + + {/if} + {:else} +

+ Page Not found +

+
+ The page you were looking for does not exist +
+ {/if} +
+
+ + diff --git a/webpage/src/routes/UserStore.svelte.ts b/webpage/src/routes/UserStore.svelte.ts new file mode 100644 index 0000000..e882482 --- /dev/null +++ b/webpage/src/routes/UserStore.svelte.ts @@ -0,0 +1,55 @@ +import { goto } from "$app/navigation"; + +type User = { + token: string, + id: string, + user_type: number, + username: string, + email: string, +}; + +export function createUserStore() { + let user = $state(undefined); + + function getValue() { + if (user == undefined) { + let storage = localStorage.getItem('user'); + if (storage) { + try { + user = JSON.parse(storage); + } catch { + user = undefined; + } + } + } + return user; + } + + return { + get user() { + return getValue(); + }, + set user(value: User | undefined) { + if (value) { + localStorage.setItem('user', JSON.stringify(value)); + } else { + localStorage.removeItem('user'); + } + user = value; + }, + checkUser(pathOnFail: string, level?: number) { + if (user && user.level > (level ?? 2) ) { + goto(pathOnFail); + return true; + } + + return false; + }, + isLogin() { + if (getValue()) return true; + return false; + } + } +} + +export const userStore = createUserStore(); diff --git a/webpage/src/routes/login/+page.svelte b/webpage/src/routes/login/+page.svelte new file mode 100644 index 0000000..41240a4 --- /dev/null +++ b/webpage/src/routes/login/+page.svelte @@ -0,0 +1,83 @@ + + + + Login + + + + + diff --git a/webpage/src/routes/logout/+page.svelte b/webpage/src/routes/logout/+page.svelte new file mode 100644 index 0000000..35bb995 --- /dev/null +++ b/webpage/src/routes/logout/+page.svelte @@ -0,0 +1,10 @@ + diff --git a/webpage/src/styles/app.css b/webpage/src/styles/app.css new file mode 100644 index 0000000..50800a7 --- /dev/null +++ b/webpage/src/styles/app.css @@ -0,0 +1,88 @@ +*{box-sizing: border-box;font-family: 'Roboto', sans-serif;} + +:root { + --nav-bar-size: 54px; + --white: #ffffff; + --grey: #dbdcde; + --light-grey: #fafafa; + --main: #fca311; + --sec: #14213d; + --black: #000000; + --red: 212, 38, 38; + --green: 92, 199, 30; +} + +body { + margin: 0px; +} + +.button, +button { + border-radius: 10px; + text-align: center; + padding: 3px 6px; + border: none; + box-shadow: 0 2px 8px 1px #66666655; + background: var(--main); + color: var(--black); + + &.padded { + padding: 10px; + } + + &.danger { + background: rgb(var(--red)); + color: white; + font-weight: bold; + } +} + +.flex { + display: flex; +} + +.justify-center { + justify-content: center; +} + +.justify-start { + justify-content: start; +} + +.justify-end { + justify-content: end; +} + +.align-center { + align-items: center; +} + +.grow-1 { + flex-grow: 1; +} + +.w100 { + width: 100%; + display: block; +} + +.text-center { + text-align: center; +} + +.simple-link { + color: var(--sec); + text-decoration: none; +} + +.bold { + font-weight: bold; +} + +.bigger { + font-size: 1.1rem; +} + +.danger { + color: red; +} diff --git a/webpage/src/styles/fonts.css b/webpage/src/styles/fonts.css new file mode 100644 index 0000000..57e7936 --- /dev/null +++ b/webpage/src/styles/fonts.css @@ -0,0 +1,8 @@ +@font-face { + font-family: MedievalSharp; + src: url(/MedievalSharp-Regular.ttf); +} + +.font-medieval { + font-family: MedievalSharp, cursive; +} diff --git a/webpage/src/styles/forms.css b/webpage/src/styles/forms.css new file mode 100644 index 0000000..b75209d --- /dev/null +++ b/webpage/src/styles/forms.css @@ -0,0 +1,147 @@ +/* forms */ + +a { + cursor: pointer; +} + +.card form { + padding: 0; + border-radius: none; + box-shadow: none; +} + +form { + padding: 30px; + margin: 20px 0; + border-radius: 10px; + box-shadow: 2px 5px 8px 2px #66666655; + + label, + fieldset legend { + display: block; + padding-bottom: 5px; + font-size: 1.2rem; + } + + input { + border: none; + box-shadow: 0 2px 5px 1px #66666655; + border-radius: 5px; + padding: 10px; + width: 100%; + } + + input:invalid:focus, + &.submitted input:invalid { + box-shadow: 0 2px 5px 1px rgba(var(--red), 0.2); + } + + &.submitted input:valid { + box-shadow: 0 2px 5px 1px rgba(var(--green), 0.2); + } + + & .spacer { + padding-bottom: 10px; + } + + fieldset { + padding-bottom: 15px; + border: none; + + .form-msg { + font-size: 0.9rem; + } + + .error { + color: rgb(var(--red)) + } + } + + button { + font-size: 1.2rem; + margin-left: 50%; + width: 50%; + transform: translateX(-50%); + padding: 10px; + } + + .input-radial { + label { + display: inline; + font-size: 1rem; + } + + input[type="radio"] { + width: auto; + box-shadow: none; + } + } + + /* Upload files */ + fieldset.file-upload { + input[type="file"] { + height: 1px; + width: 1px; + padding: 0; + box-shadow: none; + } + + .icon-holder { + padding: 10px; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + font-size: 1rem; + + .icon { + height: 150px; + width: 150px; + padding: 20px; + border-radius: 10px; + background: none; + transform: none; + margin: 0; + font-size: 1rem; + transition: all 1s; + + &.adapt { + width: auto; + height: auto; + max-width: 80%; + max-height: 80%; + min-height: 150px; + min-width: 150px; + padding: 20px; + } + + img { + display: block; + width: 100%; + height: 80%; + object-fit: contain; + text-align: center; + transition: all 1s; + } + + span { + display: block; + width: 100%; + padding-top: 10px; + text-align: center; + } + } + } + + } + + fieldset.file-upload:has(input[type="file"]:invalid:focus) .icon, + &.submitted fieldset.file-upload:has(input[type="file"]:invalid:focus) .icon { + box-shadow: 0 2px 5px 1px rgba(var(--red), 0.2); + } + + &.submitted fieldset.file-upload:has(input[type="file"]:valid:focus) .icon { + box-shadow: 0 2px 5px 1px rgba(var(--green), 0.2); + } + +} diff --git a/webpage/static/favicon.png b/webpage/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH